From 6e6ee37da0a8911fa0684434190cd9c7dbc4643d Mon Sep 17 00:00:00 2001 From: Jason Woltje Date: Wed, 1 Apr 2026 21:23:26 -0500 Subject: [PATCH] =?UTF-8?q?feat:=20complete=20framework=20migration=20?= =?UTF-8?q?=E2=80=94=20PowerShell,=20adapters,=20guides,=20profiles,=20tes?= =?UTF-8?q?ts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Completes the bootstrap repo migration with remaining files: - PowerShell scripts (.ps1) for Windows support (bin/ + tools/) - Runtime adapters (claude, codex, generic, pi) - Guides (17 .md files) and profiles (domains, tech-stacks, workflows) - Wizard test suite (6 test files from bootstrap tests/) - Memory placeholder, audit history Bootstrap repo (mosaic/bootstrap) is now fully superseded: - All 335 files accounted for - 5 build config files (package.json, tsconfig, etc.) not needed — monorepo has its own at packages/mosaic/ - skills-local/ superseded by monorepo skills/ with mosaic-* naming - src/ already lives at packages/mosaic/src/ --- eslint.config.mjs | 1 + .../__tests__/integration/full-wizard.test.ts | 109 ++ .../__tests__/stages/detect-install.test.ts | 68 + .../__tests__/stages/soul-setup.test.ts | 72 + .../__tests__/stages/user-setup.test.ts | 60 + .../__tests__/template/builders.test.ts | 97 ++ .../mosaic/__tests__/template/engine.test.ts | 52 + packages/mosaic/framework/adapters/claude.md | 17 + packages/mosaic/framework/adapters/codex.md | 13 + packages/mosaic/framework/adapters/generic.md | 14 + packages/mosaic/framework/adapters/pi.md | 36 + .../mosaic/framework/bin/mosaic-doctor.ps1 | 283 ++++ .../bin/mosaic-ensure-sequential-thinking.ps1 | 114 ++ packages/mosaic/framework/bin/mosaic-init.ps1 | 144 ++ .../bin/mosaic-link-runtime-assets.ps1 | 111 ++ .../bin/mosaic-migrate-local-skills.ps1 | 90 ++ .../framework/bin/mosaic-release-upgrade.ps1 | 65 + .../framework/bin/mosaic-sync-skills.ps1 | 126 ++ packages/mosaic/framework/bin/mosaic.ps1 | 437 ++++++ .../AUDIT-2026-02-17-framework-consistency.md | 139 ++ .../mosaic/framework/guides/AUTHENTICATION.md | 193 +++ packages/mosaic/framework/guides/BACKEND.md | 125 ++ packages/mosaic/framework/guides/BOOTSTRAP.md | 487 +++++++ .../framework/guides/CI-CD-PIPELINES.md | 1082 +++++++++++++++ .../mosaic/framework/guides/CODE-REVIEW.md | 154 +++ .../mosaic/framework/guides/DOCUMENTATION.md | 132 ++ .../mosaic/framework/guides/E2E-DELIVERY.md | 210 +++ packages/mosaic/framework/guides/FRONTEND.md | 91 ++ .../mosaic/framework/guides/INFRASTRUCTURE.md | 339 +++++ packages/mosaic/framework/guides/MEMORY.md | 51 + .../guides/ORCHESTRATOR-LEARNINGS.md | 127 ++ .../framework/guides/ORCHESTRATOR-PROTOCOL.md | 268 ++++ .../mosaic/framework/guides/ORCHESTRATOR.md | 1175 +++++++++++++++++ packages/mosaic/framework/guides/PRD.md | 63 + .../mosaic/framework/guides/QA-TESTING.md | 125 ++ .../mosaic/framework/guides/TYPESCRIPT.md | 440 ++++++ .../mosaic/framework/guides/VAULT-SECRETS.md | 205 +++ packages/mosaic/framework/install.ps1 | 231 ++++ packages/mosaic/framework/memory/.gitkeep | 0 packages/mosaic/framework/profiles/README.md | 22 + .../profiles/domains/crypto-web3.json | 190 +++ .../profiles/domains/fintech-security.json | 190 +++ .../profiles/domains/healthcare-hipaa.json | 189 +++ .../profiles/tech-stacks/nestjs-backend.json | 154 +++ .../tech-stacks/nextjs-fullstack.json | 168 +++ .../profiles/tech-stacks/python-fastapi.json | 168 +++ .../profiles/tech-stacks/react-frontend.json | 161 +++ .../profiles/workflows/api-development.json | 182 +++ .../workflows/frontend-component.json | 201 +++ .../workflows/testing-automation.json | 201 +++ packages/mosaic/framework/remote-install.ps1 | 38 + 51 files changed, 9410 insertions(+) create mode 100644 packages/mosaic/__tests__/integration/full-wizard.test.ts create mode 100644 packages/mosaic/__tests__/stages/detect-install.test.ts create mode 100644 packages/mosaic/__tests__/stages/soul-setup.test.ts create mode 100644 packages/mosaic/__tests__/stages/user-setup.test.ts create mode 100644 packages/mosaic/__tests__/template/builders.test.ts create mode 100644 packages/mosaic/__tests__/template/engine.test.ts create mode 100644 packages/mosaic/framework/adapters/claude.md create mode 100644 packages/mosaic/framework/adapters/codex.md create mode 100644 packages/mosaic/framework/adapters/generic.md create mode 100644 packages/mosaic/framework/adapters/pi.md create mode 100644 packages/mosaic/framework/bin/mosaic-doctor.ps1 create mode 100755 packages/mosaic/framework/bin/mosaic-ensure-sequential-thinking.ps1 create mode 100644 packages/mosaic/framework/bin/mosaic-init.ps1 create mode 100644 packages/mosaic/framework/bin/mosaic-link-runtime-assets.ps1 create mode 100644 packages/mosaic/framework/bin/mosaic-migrate-local-skills.ps1 create mode 100644 packages/mosaic/framework/bin/mosaic-release-upgrade.ps1 create mode 100644 packages/mosaic/framework/bin/mosaic-sync-skills.ps1 create mode 100644 packages/mosaic/framework/bin/mosaic.ps1 create mode 100644 packages/mosaic/framework/defaults/AUDIT-2026-02-17-framework-consistency.md create mode 100644 packages/mosaic/framework/guides/AUTHENTICATION.md create mode 100644 packages/mosaic/framework/guides/BACKEND.md create mode 100755 packages/mosaic/framework/guides/BOOTSTRAP.md create mode 100644 packages/mosaic/framework/guides/CI-CD-PIPELINES.md create mode 100755 packages/mosaic/framework/guides/CODE-REVIEW.md create mode 100644 packages/mosaic/framework/guides/DOCUMENTATION.md create mode 100644 packages/mosaic/framework/guides/E2E-DELIVERY.md create mode 100644 packages/mosaic/framework/guides/FRONTEND.md create mode 100644 packages/mosaic/framework/guides/INFRASTRUCTURE.md create mode 100644 packages/mosaic/framework/guides/MEMORY.md create mode 100644 packages/mosaic/framework/guides/ORCHESTRATOR-LEARNINGS.md create mode 100644 packages/mosaic/framework/guides/ORCHESTRATOR-PROTOCOL.md create mode 100644 packages/mosaic/framework/guides/ORCHESTRATOR.md create mode 100644 packages/mosaic/framework/guides/PRD.md create mode 100644 packages/mosaic/framework/guides/QA-TESTING.md create mode 100644 packages/mosaic/framework/guides/TYPESCRIPT.md create mode 100644 packages/mosaic/framework/guides/VAULT-SECRETS.md create mode 100644 packages/mosaic/framework/install.ps1 create mode 100644 packages/mosaic/framework/memory/.gitkeep create mode 100644 packages/mosaic/framework/profiles/README.md create mode 100644 packages/mosaic/framework/profiles/domains/crypto-web3.json create mode 100644 packages/mosaic/framework/profiles/domains/fintech-security.json create mode 100644 packages/mosaic/framework/profiles/domains/healthcare-hipaa.json create mode 100644 packages/mosaic/framework/profiles/tech-stacks/nestjs-backend.json create mode 100644 packages/mosaic/framework/profiles/tech-stacks/nextjs-fullstack.json create mode 100644 packages/mosaic/framework/profiles/tech-stacks/python-fastapi.json create mode 100644 packages/mosaic/framework/profiles/tech-stacks/react-frontend.json create mode 100644 packages/mosaic/framework/profiles/workflows/api-development.json create mode 100644 packages/mosaic/framework/profiles/workflows/frontend-component.json create mode 100644 packages/mosaic/framework/profiles/workflows/testing-automation.json create mode 100644 packages/mosaic/framework/remote-install.ps1 diff --git a/eslint.config.mjs b/eslint.config.mjs index 8c88d1c..1a2c789 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -11,6 +11,7 @@ export default tseslint.config( '**/coverage/**', '**/drizzle.config.ts', '**/framework/**', + 'packages/mosaic/__tests__/**', ], }, { diff --git a/packages/mosaic/__tests__/integration/full-wizard.test.ts b/packages/mosaic/__tests__/integration/full-wizard.test.ts new file mode 100644 index 0000000..c46242b --- /dev/null +++ b/packages/mosaic/__tests__/integration/full-wizard.test.ts @@ -0,0 +1,109 @@ +import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import { + mkdtempSync, + mkdirSync, + writeFileSync, + readFileSync, + existsSync, + rmSync, + cpSync, +} from 'node:fs'; +import { join } from 'node:path'; +import { tmpdir } from 'node:os'; +import { HeadlessPrompter } from '../../src/prompter/headless-prompter.js'; +import { createConfigService } from '../../src/config/config-service.js'; +import { runWizard } from '../../src/wizard.js'; + +describe('Full Wizard (headless)', () => { + let tmpDir: string; + const repoRoot = join(import.meta.dirname, '..', '..'); + + beforeEach(() => { + tmpDir = mkdtempSync(join(tmpdir(), 'mosaic-wizard-test-')); + // Copy templates to tmp dir + const templatesDir = join(repoRoot, 'templates'); + if (existsSync(templatesDir)) { + cpSync(templatesDir, join(tmpDir, 'templates'), { recursive: true }); + } + }); + + afterEach(() => { + rmSync(tmpDir, { recursive: true, force: true }); + }); + + it('quick start produces valid SOUL.md', async () => { + const prompter = new HeadlessPrompter({ + 'Installation mode': 'quick', + 'What name should agents use?': 'TestBot', + 'Communication style': 'direct', + 'Your name': 'Tester', + 'Your pronouns': 'They/Them', + 'Your timezone': 'UTC', + }); + + await runWizard({ + mosaicHome: tmpDir, + sourceDir: tmpDir, + prompter, + configService: createConfigService(tmpDir, tmpDir), + }); + + const soulPath = join(tmpDir, 'SOUL.md'); + expect(existsSync(soulPath)).toBe(true); + + const soul = readFileSync(soulPath, 'utf-8'); + expect(soul).toContain('You are **TestBot**'); + expect(soul).toContain('Be direct, concise, and concrete'); + expect(soul).toContain('execution partner and visibility engine'); + }); + + it('quick start produces valid USER.md', async () => { + const prompter = new HeadlessPrompter({ + 'Installation mode': 'quick', + 'What name should agents use?': 'TestBot', + 'Communication style': 'direct', + 'Your name': 'Tester', + 'Your pronouns': 'He/Him', + 'Your timezone': 'America/Chicago', + }); + + await runWizard({ + mosaicHome: tmpDir, + sourceDir: tmpDir, + prompter, + configService: createConfigService(tmpDir, tmpDir), + }); + + const userPath = join(tmpDir, 'USER.md'); + expect(existsSync(userPath)).toBe(true); + + const user = readFileSync(userPath, 'utf-8'); + expect(user).toContain('**Name:** Tester'); + expect(user).toContain('**Pronouns:** He/Him'); + expect(user).toContain('**Timezone:** America/Chicago'); + }); + + it('applies CLI overrides', async () => { + const prompter = new HeadlessPrompter({ + 'Installation mode': 'quick', + 'Your name': 'FromPrompt', + }); + + await runWizard({ + mosaicHome: tmpDir, + sourceDir: tmpDir, + prompter, + configService: createConfigService(tmpDir, tmpDir), + cliOverrides: { + soul: { + agentName: 'FromCLI', + communicationStyle: 'formal', + }, + }, + }); + + const soul = readFileSync(join(tmpDir, 'SOUL.md'), 'utf-8'); + expect(soul).toContain('You are **FromCLI**'); + expect(soul).toContain('Use professional, structured language'); + }); +}); diff --git a/packages/mosaic/__tests__/stages/detect-install.test.ts b/packages/mosaic/__tests__/stages/detect-install.test.ts new file mode 100644 index 0000000..bc10659 --- /dev/null +++ b/packages/mosaic/__tests__/stages/detect-install.test.ts @@ -0,0 +1,68 @@ +import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs'; +import { join } from 'node:path'; +import { tmpdir } from 'node:os'; +import { HeadlessPrompter } from '../../src/prompter/headless-prompter.js'; +import { detectInstallStage } from '../../src/stages/detect-install.js'; +import type { WizardState } from '../../src/types.js'; +import type { ConfigService } from '../../src/config/config-service.js'; + +function createState(mosaicHome: string): WizardState { + return { + mosaicHome, + sourceDir: mosaicHome, + mode: 'quick', + installAction: 'fresh', + soul: {}, + user: {}, + tools: {}, + runtimes: { detected: [], mcpConfigured: false }, + selectedSkills: [], + }; +} + +const mockConfig: ConfigService = { + readSoul: async () => ({ agentName: 'TestAgent' }), + readUser: async () => ({ userName: 'TestUser' }), + readTools: async () => ({}), + writeSoul: async () => {}, + writeUser: async () => {}, + writeTools: async () => {}, + syncFramework: async () => {}, +}; + +describe('detectInstallStage', () => { + let tmpDir: string; + + beforeEach(() => { + tmpDir = mkdtempSync(join(tmpdir(), 'mosaic-test-')); + }); + + afterEach(() => { + rmSync(tmpDir, { recursive: true, force: true }); + }); + + it('sets fresh for empty directory', async () => { + const p = new HeadlessPrompter({}); + const state = createState(join(tmpDir, 'nonexistent')); + await detectInstallStage(p, state, mockConfig); + + expect(state.installAction).toBe('fresh'); + }); + + it('detects existing install and offers choices', async () => { + // Create a mock existing install + mkdirSync(join(tmpDir, 'bin'), { recursive: true }); + writeFileSync(join(tmpDir, 'AGENTS.md'), '# Test'); + writeFileSync(join(tmpDir, 'SOUL.md'), 'You are **Jarvis** in this session.'); + + const p = new HeadlessPrompter({ + 'What would you like to do?': 'keep', + }); + const state = createState(tmpDir); + await detectInstallStage(p, state, mockConfig); + + expect(state.installAction).toBe('keep'); + expect(state.soul.agentName).toBe('TestAgent'); + }); +}); diff --git a/packages/mosaic/__tests__/stages/soul-setup.test.ts b/packages/mosaic/__tests__/stages/soul-setup.test.ts new file mode 100644 index 0000000..316884d --- /dev/null +++ b/packages/mosaic/__tests__/stages/soul-setup.test.ts @@ -0,0 +1,72 @@ +import { describe, it, expect } from 'vitest'; +import { HeadlessPrompter } from '../../src/prompter/headless-prompter.js'; +import { soulSetupStage } from '../../src/stages/soul-setup.js'; +import type { WizardState } from '../../src/types.js'; + +function createState(overrides: Partial = {}): WizardState { + return { + mosaicHome: '/tmp/test-mosaic', + sourceDir: '/tmp/test-mosaic', + mode: 'quick', + installAction: 'fresh', + soul: {}, + user: {}, + tools: {}, + runtimes: { detected: [], mcpConfigured: false }, + selectedSkills: [], + ...overrides, + }; +} + +describe('soulSetupStage', () => { + it('sets agent name and style in quick mode', async () => { + const p = new HeadlessPrompter({ + 'What name should agents use?': 'Jarvis', + 'Communication style': 'friendly', + }); + + const state = createState({ mode: 'quick' }); + await soulSetupStage(p, state); + + expect(state.soul.agentName).toBe('Jarvis'); + expect(state.soul.communicationStyle).toBe('friendly'); + expect(state.soul.roleDescription).toBe('execution partner and visibility engine'); + }); + + it('uses defaults in quick mode with no answers', async () => { + const p = new HeadlessPrompter({}); + const state = createState({ mode: 'quick' }); + await soulSetupStage(p, state); + + expect(state.soul.agentName).toBe('Assistant'); + expect(state.soul.communicationStyle).toBe('direct'); + }); + + it('skips when install action is keep', async () => { + const p = new HeadlessPrompter({}); + const state = createState({ installAction: 'keep' }); + state.soul.agentName = 'Existing'; + await soulSetupStage(p, state); + + expect(state.soul.agentName).toBe('Existing'); + }); + + it('asks for all fields in advanced mode', async () => { + const p = new HeadlessPrompter({ + 'What name should agents use?': 'Atlas', + 'Agent role description': 'memory keeper', + 'Communication style': 'formal', + 'Accessibility preferences': 'ADHD-friendly', + 'Custom guardrails (optional)': 'Never push to main', + }); + + const state = createState({ mode: 'advanced' }); + await soulSetupStage(p, state); + + expect(state.soul.agentName).toBe('Atlas'); + expect(state.soul.roleDescription).toBe('memory keeper'); + expect(state.soul.communicationStyle).toBe('formal'); + expect(state.soul.accessibility).toBe('ADHD-friendly'); + expect(state.soul.customGuardrails).toBe('Never push to main'); + }); +}); diff --git a/packages/mosaic/__tests__/stages/user-setup.test.ts b/packages/mosaic/__tests__/stages/user-setup.test.ts new file mode 100644 index 0000000..f20ad65 --- /dev/null +++ b/packages/mosaic/__tests__/stages/user-setup.test.ts @@ -0,0 +1,60 @@ +import { describe, it, expect } from 'vitest'; +import { HeadlessPrompter } from '../../src/prompter/headless-prompter.js'; +import { userSetupStage } from '../../src/stages/user-setup.js'; +import type { WizardState } from '../../src/types.js'; + +function createState(overrides: Partial = {}): WizardState { + return { + mosaicHome: '/tmp/test-mosaic', + sourceDir: '/tmp/test-mosaic', + mode: 'quick', + installAction: 'fresh', + soul: { communicationStyle: 'direct' }, + user: {}, + tools: {}, + runtimes: { detected: [], mcpConfigured: false }, + selectedSkills: [], + ...overrides, + }; +} + +describe('userSetupStage', () => { + it('collects basic info in quick mode', async () => { + const p = new HeadlessPrompter({ + 'Your name': 'Jason', + 'Your pronouns': 'He/Him', + 'Your timezone': 'America/Chicago', + }); + + const state = createState({ mode: 'quick' }); + await userSetupStage(p, state); + + expect(state.user.userName).toBe('Jason'); + expect(state.user.pronouns).toBe('He/Him'); + expect(state.user.timezone).toBe('America/Chicago'); + expect(state.user.communicationPrefs).toContain('Direct and concise'); + }); + + it('skips when install action is keep', async () => { + const p = new HeadlessPrompter({}); + const state = createState({ installAction: 'keep' }); + state.user.userName = 'Existing'; + await userSetupStage(p, state); + + expect(state.user.userName).toBe('Existing'); + }); + + it('derives communication prefs from soul style', async () => { + const p = new HeadlessPrompter({ + 'Your name': 'Test', + }); + + const state = createState({ + mode: 'quick', + soul: { communicationStyle: 'friendly' }, + }); + await userSetupStage(p, state); + + expect(state.user.communicationPrefs).toContain('Warm and conversational'); + }); +}); diff --git a/packages/mosaic/__tests__/template/builders.test.ts b/packages/mosaic/__tests__/template/builders.test.ts new file mode 100644 index 0000000..fa07946 --- /dev/null +++ b/packages/mosaic/__tests__/template/builders.test.ts @@ -0,0 +1,97 @@ +import { describe, it, expect } from 'vitest'; +import { + buildSoulTemplateVars, + buildUserTemplateVars, + buildToolsTemplateVars, +} from '../../src/template/builders.js'; + +describe('buildSoulTemplateVars', () => { + it('builds direct style correctly', () => { + const vars = buildSoulTemplateVars({ + agentName: 'Jarvis', + communicationStyle: 'direct', + }); + expect(vars.AGENT_NAME).toBe('Jarvis'); + expect(vars.BEHAVIORAL_PRINCIPLES).toContain('Clarity over performance theater'); + expect(vars.COMMUNICATION_STYLE).toContain('Be direct, concise, and concrete'); + }); + + it('builds friendly style correctly', () => { + const vars = buildSoulTemplateVars({ + communicationStyle: 'friendly', + }); + expect(vars.BEHAVIORAL_PRINCIPLES).toContain('Be helpful and approachable'); + expect(vars.COMMUNICATION_STYLE).toContain('Be warm and conversational'); + }); + + it('builds formal style correctly', () => { + const vars = buildSoulTemplateVars({ + communicationStyle: 'formal', + }); + expect(vars.BEHAVIORAL_PRINCIPLES).toContain('Maintain professional, structured'); + expect(vars.COMMUNICATION_STYLE).toContain('Use professional, structured language'); + }); + + it('appends accessibility to principles', () => { + const vars = buildSoulTemplateVars({ + communicationStyle: 'direct', + accessibility: 'ADHD-friendly chunking', + }); + expect(vars.BEHAVIORAL_PRINCIPLES).toContain('6. ADHD-friendly chunking.'); + }); + + it('does not append accessibility when "none"', () => { + const vars = buildSoulTemplateVars({ + communicationStyle: 'direct', + accessibility: 'none', + }); + expect(vars.BEHAVIORAL_PRINCIPLES).not.toContain('6.'); + }); + + it('formats custom guardrails', () => { + const vars = buildSoulTemplateVars({ + customGuardrails: 'Never auto-commit', + }); + expect(vars.CUSTOM_GUARDRAILS).toBe('- Never auto-commit'); + }); + + it('uses defaults when config is empty', () => { + const vars = buildSoulTemplateVars({}); + expect(vars.AGENT_NAME).toBe('Assistant'); + expect(vars.ROLE_DESCRIPTION).toBe('execution partner and visibility engine'); + }); +}); + +describe('buildUserTemplateVars', () => { + it('maps all fields', () => { + const vars = buildUserTemplateVars({ + userName: 'Jason', + pronouns: 'He/Him', + timezone: 'America/Chicago', + }); + expect(vars.USER_NAME).toBe('Jason'); + expect(vars.PRONOUNS).toBe('He/Him'); + expect(vars.TIMEZONE).toBe('America/Chicago'); + }); + + it('uses defaults for missing fields', () => { + const vars = buildUserTemplateVars({}); + expect(vars.PRONOUNS).toBe('They/Them'); + expect(vars.TIMEZONE).toBe('UTC'); + }); +}); + +describe('buildToolsTemplateVars', () => { + it('builds git providers table', () => { + const vars = buildToolsTemplateVars({ + gitProviders: [{ name: 'GitHub', url: 'https://github.com', cli: 'gh', purpose: 'OSS' }], + }); + expect(vars.GIT_PROVIDERS_TABLE).toContain('| GitHub |'); + expect(vars.GIT_PROVIDERS_TABLE).toContain('`gh`'); + }); + + it('uses default table when no providers', () => { + const vars = buildToolsTemplateVars({}); + expect(vars.GIT_PROVIDERS_TABLE).toContain('add your git providers here'); + }); +}); diff --git a/packages/mosaic/__tests__/template/engine.test.ts b/packages/mosaic/__tests__/template/engine.test.ts new file mode 100644 index 0000000..f8591f3 --- /dev/null +++ b/packages/mosaic/__tests__/template/engine.test.ts @@ -0,0 +1,52 @@ +import { describe, it, expect } from 'vitest'; +import { renderTemplate } from '../../src/template/engine.js'; + +describe('renderTemplate', () => { + it('replaces all placeholders', () => { + const template = 'You are **{{AGENT_NAME}}**, role: {{ROLE_DESCRIPTION}}'; + const result = renderTemplate(template, { + AGENT_NAME: 'Jarvis', + ROLE_DESCRIPTION: 'steward', + }); + expect(result).toBe('You are **Jarvis**, role: steward'); + }); + + it('preserves ${ENV_VAR} references', () => { + const template = 'Path: ${HOME}/.config, Agent: {{AGENT_NAME}}'; + const result = renderTemplate(template, { AGENT_NAME: 'Test' }); + expect(result).toBe('Path: ${HOME}/.config, Agent: Test'); + }); + + it('handles multi-line values', () => { + const template = '{{PRINCIPLES}}'; + const result = renderTemplate(template, { + PRINCIPLES: '1. First\n2. Second\n3. Third', + }); + expect(result).toBe('1. First\n2. Second\n3. Third'); + }); + + it('replaces unset vars with empty string by default', () => { + const template = 'Before {{MISSING}} After'; + const result = renderTemplate(template, {}); + expect(result).toBe('Before After'); + }); + + it('throws in strict mode for missing vars', () => { + const template = '{{MISSING}}'; + expect(() => renderTemplate(template, {}, { strict: true })).toThrow( + 'Template variable not provided: {{MISSING}}', + ); + }); + + it('handles multiple occurrences of same placeholder', () => { + const template = '{{NAME}} says hello, {{NAME}}!'; + const result = renderTemplate(template, { NAME: 'Jarvis' }); + expect(result).toBe('Jarvis says hello, Jarvis!'); + }); + + it('preserves non-placeholder curly braces', () => { + const template = 'const x = { foo: {{VALUE}} }'; + const result = renderTemplate(template, { VALUE: '"bar"' }); + expect(result).toBe('const x = { foo: "bar" }'); + }); +}); diff --git a/packages/mosaic/framework/adapters/claude.md b/packages/mosaic/framework/adapters/claude.md new file mode 100644 index 0000000..d8abaa5 --- /dev/null +++ b/packages/mosaic/framework/adapters/claude.md @@ -0,0 +1,17 @@ +# Claude Adapter + +Use this adapter when running Claude CLI sessions. + +## Required Context + +1. `~/.config/mosaic/STANDARDS.md` +2. `/AGENTS.md` + +## Command Wrapper + +Use wrapper commands from `~/.config/mosaic/bin/` for lifecycle rituals. + +## Migration Note + +Project-local `.claude/commands/*.md` should call `scripts/agent/*.sh` so behavior stays runtime-neutral. +Guides and tools should resolve to `~/.config/mosaic/guides` and `~/.config/mosaic/tools` (linked into `~/.claude` for compatibility). diff --git a/packages/mosaic/framework/adapters/codex.md b/packages/mosaic/framework/adapters/codex.md new file mode 100644 index 0000000..0609a3b --- /dev/null +++ b/packages/mosaic/framework/adapters/codex.md @@ -0,0 +1,13 @@ +# Codex Adapter + +Use this adapter when running Codex CLI sessions. + +## Required Context + +1. `~/.config/mosaic/STANDARDS.md` +2. `/AGENTS.md` + +## Runtime Behavior + +- Favor repo lifecycle scripts under `scripts/agent/` for start/end rituals. +- Keep instructions and quality gates aligned with Mosaic standards. diff --git a/packages/mosaic/framework/adapters/generic.md b/packages/mosaic/framework/adapters/generic.md new file mode 100644 index 0000000..548946a --- /dev/null +++ b/packages/mosaic/framework/adapters/generic.md @@ -0,0 +1,14 @@ +# Generic Adapter + +For runtimes without a first-class adapter yet. + +## Required Context + +1. Load `~/.config/mosaic/STANDARDS.md` +2. Load project `AGENTS.md` + +## Minimal Contract + +- Use `scripts/agent/session-start.sh` at start if present. +- Use `scripts/agent/session-end.sh` before completion if present. +- If missing, run equivalent repo commands and report what was executed. diff --git a/packages/mosaic/framework/adapters/pi.md b/packages/mosaic/framework/adapters/pi.md new file mode 100644 index 0000000..9837ea0 --- /dev/null +++ b/packages/mosaic/framework/adapters/pi.md @@ -0,0 +1,36 @@ +# Pi Adapter + +Use this adapter when running Pi sessions via `mosaic pi`. + +## Required Context + +1. `~/.config/mosaic/STANDARDS.md` +2. `/AGENTS.md` + +## Integration + +Pi is the native Mosaic agent runtime. The `mosaic pi` launcher: + +1. Injects the full runtime contract via `--append-system-prompt` +2. Loads Mosaic skills via `--skill` flags +3. Loads the Mosaic extension via `--extension` for lifecycle hooks +4. Detects active missions and injects initial prompts + +## Capabilities vs Other Runtimes + +- No permission restrictions (no yolo flag needed) +- Native thinking levels replace sequential-thinking MCP +- Native skill discovery compatible with Mosaic SKILL.md format +- Native extension system for lifecycle hooks (TypeScript, not bash shims) +- Native session persistence and resume +- Model-agnostic (Anthropic, OpenAI, Google, Ollama, custom providers) + +## Command Wrapper + +```bash +mosaic pi # Interactive session +mosaic pi "Fix the auth bug" # With initial prompt +mosaic yolo pi # Identical to mosaic pi +mosaic coord --pi run # Coordinator-driven session +mosaic prdy --pi init # PRD creation via Pi +``` diff --git a/packages/mosaic/framework/bin/mosaic-doctor.ps1 b/packages/mosaic/framework/bin/mosaic-doctor.ps1 new file mode 100644 index 0000000..6a43dd5 --- /dev/null +++ b/packages/mosaic/framework/bin/mosaic-doctor.ps1 @@ -0,0 +1,283 @@ +# mosaic-doctor.ps1 +# Audits Mosaic runtime state and detects drift across agent runtimes. +# PowerShell equivalent of mosaic-doctor (bash). +$ErrorActionPreference = "Stop" + +param( + [switch]$FailOnWarn, + [switch]$Verbose, + [switch]$Help +) + +$MosaicHome = if ($env:MOSAIC_HOME) { $env:MOSAIC_HOME } else { Join-Path $env:USERPROFILE ".config\mosaic" } + +if ($Help) { + Write-Host @" +Usage: mosaic-doctor.ps1 [-FailOnWarn] [-Verbose] [-Help] + +Audit Mosaic runtime state and detect drift across agent runtimes. +"@ + exit 0 +} + +$script:warnCount = 0 + +function Warn { + param([string]$Message) + $script:warnCount++ + Write-Host "[WARN] $Message" -ForegroundColor Yellow +} + +function Pass { + param([string]$Message) + if ($Verbose) { Write-Host "[OK] $Message" -ForegroundColor Green } +} + +function Expect-Dir { + param([string]$Path) + if (-not (Test-Path $Path -PathType Container)) { Warn "Missing directory: $Path" } + else { Pass "Directory present: $Path" } +} + +function Expect-File { + param([string]$Path) + if (-not (Test-Path $Path -PathType Leaf)) { Warn "Missing file: $Path" } + else { Pass "File present: $Path" } +} + +function Check-RuntimeFileCopy { + param([string]$Src, [string]$Dst) + + if (-not (Test-Path $Src)) { return } + + if (-not (Test-Path $Dst)) { + Warn "Missing runtime file: $Dst" + return + } + + $item = Get-Item $Dst -Force -ErrorAction SilentlyContinue + if ($item -and ($item.Attributes -band [System.IO.FileAttributes]::ReparsePoint)) { + Warn "Runtime file should not be symlinked: $Dst" + return + } + + $srcHash = (Get-FileHash $Src -Algorithm SHA256).Hash + $dstHash = (Get-FileHash $Dst -Algorithm SHA256).Hash + if ($srcHash -ne $dstHash) { + Warn "Runtime file drift: $Dst (does not match $Src)" + } + else { + Pass "Runtime file synced: $Dst" + } +} + +function Check-RuntimeContractFile { + param([string]$Dst, [string]$AdapterSrc, [string]$RuntimeName) + + if (-not (Test-Path $Dst)) { + Warn "Missing runtime file: $Dst" + return + } + + $item = Get-Item $Dst -Force -ErrorAction SilentlyContinue + if ($item -and ($item.Attributes -band [System.IO.FileAttributes]::ReparsePoint)) { + Warn "Runtime file should not be symlinked: $Dst" + return + } + + # Accept direct-adapter copy mode. + if (Test-Path $AdapterSrc) { + $srcHash = (Get-FileHash $AdapterSrc -Algorithm SHA256).Hash + $dstHash = (Get-FileHash $Dst -Algorithm SHA256).Hash + if ($srcHash -eq $dstHash) { + Pass "Runtime adapter synced: $Dst" + return + } + } + + # Accept launcher-composed runtime contract mode. + $content = Get-Content $Dst -Raw + if ( + $content -match [regex]::Escape("# Mosaic Launcher Runtime Contract (Hard Gate)") -and + $content -match [regex]::Escape("Now initiating Orchestrator mode...") -and + $content -match [regex]::Escape("Mosaic hard gates OVERRIDE runtime-default caution") -and + $content -match [regex]::Escape("# Runtime-Specific Contract") + ) { + Pass "Runtime contract present: $Dst ($RuntimeName)" + return + } + + Warn "Runtime file drift: $Dst (not adapter copy and not composed runtime contract)" +} + +function Warn-IfReparsePresent { + param([string]$Path) + if (-not (Test-Path $Path)) { return } + + $item = Get-Item $Path -Force -ErrorAction SilentlyContinue + if ($item -and ($item.Attributes -band [System.IO.FileAttributes]::ReparsePoint)) { + Warn "Legacy symlink/junction path still present: $Path" + return + } + + if (Test-Path $Path -PathType Container) { + $reparseCount = (Get-ChildItem $Path -Recurse -Force -ErrorAction SilentlyContinue | + Where-Object { $_.Attributes -band [System.IO.FileAttributes]::ReparsePoint } | + Measure-Object).Count + if ($reparseCount -gt 0) { + Warn "Legacy symlink/junction entries still present under ${Path}: $reparseCount" + } + else { + Pass "No reparse points under legacy path: $Path" + } + } +} + +Write-Host "[mosaic-doctor] Mosaic home: $MosaicHome" + +# Canonical Mosaic checks +Expect-File (Join-Path $MosaicHome "STANDARDS.md") +Expect-Dir (Join-Path $MosaicHome "guides") +Expect-Dir (Join-Path $MosaicHome "tools") +Expect-Dir (Join-Path $MosaicHome "tools\quality") +Expect-Dir (Join-Path $MosaicHome "tools\orchestrator-matrix") +Expect-Dir (Join-Path $MosaicHome "profiles") +Expect-Dir (Join-Path $MosaicHome "templates\agent") +Expect-Dir (Join-Path $MosaicHome "skills") +Expect-Dir (Join-Path $MosaicHome "skills-local") +Expect-File (Join-Path $MosaicHome "bin\mosaic-link-runtime-assets") +Expect-File (Join-Path $MosaicHome "bin\mosaic-ensure-sequential-thinking.ps1") +Expect-File (Join-Path $MosaicHome "bin\mosaic-sync-skills") +Expect-File (Join-Path $MosaicHome "bin\mosaic-projects") +Expect-File (Join-Path $MosaicHome "bin\mosaic-quality-apply") +Expect-File (Join-Path $MosaicHome "bin\mosaic-quality-verify") +Expect-File (Join-Path $MosaicHome "bin\mosaic-orchestrator-run") +Expect-File (Join-Path $MosaicHome "bin\mosaic-orchestrator-sync-tasks") +Expect-File (Join-Path $MosaicHome "bin\mosaic-orchestrator-drain") +Expect-File (Join-Path $MosaicHome "bin\mosaic-orchestrator-matrix-publish") +Expect-File (Join-Path $MosaicHome "bin\mosaic-orchestrator-matrix-consume") +Expect-File (Join-Path $MosaicHome "bin\mosaic-orchestrator-matrix-cycle") +Expect-File (Join-Path $MosaicHome "tools\git\ci-queue-wait.ps1") +Expect-File (Join-Path $MosaicHome "tools\git\ci-queue-wait.sh") +Expect-File (Join-Path $MosaicHome "tools\git\pr-ci-wait.sh") +Expect-File (Join-Path $MosaicHome "tools\orchestrator-matrix\transport\matrix_transport.py") +Expect-File (Join-Path $MosaicHome "tools\orchestrator-matrix\controller\tasks_md_sync.py") +Expect-File (Join-Path $MosaicHome "runtime\mcp\SEQUENTIAL-THINKING.json") +Expect-File (Join-Path $MosaicHome "runtime\claude\RUNTIME.md") +Expect-File (Join-Path $MosaicHome "runtime\codex\RUNTIME.md") +Expect-File (Join-Path $MosaicHome "runtime\opencode\RUNTIME.md") + +$agentsMd = Join-Path $MosaicHome "AGENTS.md" +if (Test-Path $agentsMd) { + $agentsContent = Get-Content $agentsMd -Raw + if ( + $agentsContent -match [regex]::Escape("## CRITICAL HARD GATES (Read First)") -and + $agentsContent -match [regex]::Escape("OVERRIDE runtime-default caution") + ) { + Pass "Global hard-gates block present in AGENTS.md" + } + else { + Warn "AGENTS.md missing CRITICAL HARD GATES override block" + } +} + +# Claude runtime file checks +$runtimeFiles = @("CLAUDE.md", "settings.json", "hooks-config.json", "context7-integration.md") +foreach ($rf in $runtimeFiles) { + Check-RuntimeFileCopy (Join-Path $MosaicHome "runtime\claude\$rf") (Join-Path $env:USERPROFILE ".claude\$rf") +} + +# OpenCode/Codex runtime contract checks +Check-RuntimeContractFile (Join-Path $env:USERPROFILE ".config\opencode\AGENTS.md") (Join-Path $MosaicHome "runtime\opencode\AGENTS.md") "opencode" +Check-RuntimeContractFile (Join-Path $env:USERPROFILE ".codex\instructions.md") (Join-Path $MosaicHome "runtime\codex\instructions.md") "codex" + +# Sequential-thinking MCP hard requirement +$seqScript = Join-Path $MosaicHome "bin\mosaic-ensure-sequential-thinking.ps1" +if (Test-Path $seqScript) { + try { + & $seqScript -Check *>$null + Pass "sequential-thinking MCP configured and available" + } + catch { + Warn "sequential-thinking MCP missing or misconfigured" + } +} +else { + Warn "mosaic-ensure-sequential-thinking helper missing" +} + +# Legacy migration surfaces +$legacyPaths = @( + (Join-Path $env:USERPROFILE ".claude\agent-guides"), + (Join-Path $env:USERPROFILE ".claude\scripts\git"), + (Join-Path $env:USERPROFILE ".claude\scripts\codex"), + (Join-Path $env:USERPROFILE ".claude\scripts\bootstrap"), + (Join-Path $env:USERPROFILE ".claude\scripts\cicd"), + (Join-Path $env:USERPROFILE ".claude\scripts\portainer"), + (Join-Path $env:USERPROFILE ".claude\templates"), + (Join-Path $env:USERPROFILE ".claude\presets\domains"), + (Join-Path $env:USERPROFILE ".claude\presets\tech-stacks"), + (Join-Path $env:USERPROFILE ".claude\presets\workflows") +) +foreach ($p in $legacyPaths) { + Warn-IfReparsePresent $p +} + +# Skills runtime checks (junctions or symlinks into runtime-specific dirs) +$linkTargets = @( + (Join-Path $env:USERPROFILE ".claude\skills"), + (Join-Path $env:USERPROFILE ".codex\skills"), + (Join-Path $env:USERPROFILE ".config\opencode\skills") +) + +$skillSources = @($MosaicHome + "\skills", $MosaicHome + "\skills-local") + +foreach ($runtimeSkills in $linkTargets) { + if (-not (Test-Path $runtimeSkills)) { continue } + + foreach ($sourceDir in $skillSources) { + if (-not (Test-Path $sourceDir)) { continue } + + Get-ChildItem $sourceDir -Directory | Where-Object { -not $_.Name.StartsWith(".") } | ForEach-Object { + $name = $_.Name + $skillPath = $_.FullName + $target = Join-Path $runtimeSkills $name + + if (-not (Test-Path $target)) { + Warn "Missing skill link: $target" + return + } + + $item = Get-Item $target -Force -ErrorAction SilentlyContinue + if (-not ($item.Attributes -band [System.IO.FileAttributes]::ReparsePoint)) { + Warn "Non-junction skill entry: $target" + return + } + + $targetResolved = $item.Target + if (-not $targetResolved -or (Resolve-Path $targetResolved -ErrorAction SilentlyContinue).Path -ne (Resolve-Path $skillPath -ErrorAction SilentlyContinue).Path) { + Warn "Drifted skill link: $target (expected -> $skillPath)" + } + else { + Pass "Linked skill: $target" + } + } + } +} + +# Broken junctions/symlinks in managed runtime skill dirs +$brokenLinks = 0 +foreach ($d in $linkTargets) { + if (-not (Test-Path $d)) { continue } + Get-ChildItem $d -Force -ErrorAction SilentlyContinue | Where-Object { + ($_.Attributes -band [System.IO.FileAttributes]::ReparsePoint) -and -not (Test-Path $_.FullName) + } | ForEach-Object { $brokenLinks++ } +} +if ($brokenLinks -gt 0) { + Warn "Broken skill junctions/symlinks detected: $brokenLinks" +} + +Write-Host "[mosaic-doctor] warnings=$($script:warnCount)" +if ($FailOnWarn -and $script:warnCount -gt 0) { + exit 1 +} diff --git a/packages/mosaic/framework/bin/mosaic-ensure-sequential-thinking.ps1 b/packages/mosaic/framework/bin/mosaic-ensure-sequential-thinking.ps1 new file mode 100755 index 0000000..4282c64 --- /dev/null +++ b/packages/mosaic/framework/bin/mosaic-ensure-sequential-thinking.ps1 @@ -0,0 +1,114 @@ +# mosaic-ensure-sequential-thinking.ps1 +param( + [switch]$Check +) + +$ErrorActionPreference = "Stop" + +$Pkg = "@modelcontextprotocol/server-sequential-thinking" + +function Require-Binary { + param([string]$Name) + if (-not (Get-Command $Name -ErrorAction SilentlyContinue)) { + throw "Required binary missing: $Name" + } +} + +function Warm-Package { + $null = & npx -y $Pkg --help 2>$null +} + +function Set-ClaudeConfig { + $path = Join-Path $env:USERPROFILE ".claude\settings.json" + New-Item -ItemType Directory -Path (Split-Path $path -Parent) -Force | Out-Null + + $data = @{} + if (Test-Path $path) { + try { $data = Get-Content $path -Raw | ConvertFrom-Json -AsHashtable } catch { $data = @{} } + } + if (-not $data.ContainsKey("mcpServers") -or -not ($data["mcpServers"] -is [hashtable])) { + $data["mcpServers"] = @{} + } + $data["mcpServers"]["sequential-thinking"] = @{ + command = "npx" + args = @("-y", "@modelcontextprotocol/server-sequential-thinking") + } + $data | ConvertTo-Json -Depth 20 | Set-Content -Path $path -Encoding UTF8 +} + +function Set-CodexConfig { + $path = Join-Path $env:USERPROFILE ".codex\config.toml" + New-Item -ItemType Directory -Path (Split-Path $path -Parent) -Force | Out-Null + if (-not (Test-Path $path)) { New-Item -ItemType File -Path $path -Force | Out-Null } + + $content = Get-Content $path -Raw + $content = [regex]::Replace($content, "(?ms)^\[mcp_servers\.(sequential-thinking|sequential_thinking)\].*?(?=^\[|\z)", "") + $content = $content.TrimEnd() + "`n`n[mcp_servers.sequential-thinking]`ncommand = `"npx`"`nargs = [`"-y`", `"@modelcontextprotocol/server-sequential-thinking`"]`n" + Set-Content -Path $path -Value $content -Encoding UTF8 +} + +function Set-OpenCodeConfig { + $path = Join-Path $env:USERPROFILE ".config\opencode\config.json" + New-Item -ItemType Directory -Path (Split-Path $path -Parent) -Force | Out-Null + + $data = @{} + if (Test-Path $path) { + try { $data = Get-Content $path -Raw | ConvertFrom-Json -AsHashtable } catch { $data = @{} } + } + if (-not $data.ContainsKey("mcp") -or -not ($data["mcp"] -is [hashtable])) { + $data["mcp"] = @{} + } + $data["mcp"]["sequential-thinking"] = @{ + type = "local" + command = @("npx", "-y", "@modelcontextprotocol/server-sequential-thinking") + enabled = $true + } + $data | ConvertTo-Json -Depth 20 | Set-Content -Path $path -Encoding UTF8 +} + +function Test-Configs { + $claudeOk = $false + $codexOk = $false + $opencodeOk = $false + + $claudePath = Join-Path $env:USERPROFILE ".claude\settings.json" + if (Test-Path $claudePath) { + try { + $c = Get-Content $claudePath -Raw | ConvertFrom-Json -AsHashtable + $claudeOk = $c.ContainsKey("mcpServers") -and $c["mcpServers"].ContainsKey("sequential-thinking") + } catch {} + } + + $codexPath = Join-Path $env:USERPROFILE ".codex\config.toml" + if (Test-Path $codexPath) { + $raw = Get-Content $codexPath -Raw + $codexOk = $raw -match "\[mcp_servers\.(sequential-thinking|sequential_thinking)\]" -and $raw -match "@modelcontextprotocol/server-sequential-thinking" + } + + $opencodePath = Join-Path $env:USERPROFILE ".config\opencode\config.json" + if (Test-Path $opencodePath) { + try { + $o = Get-Content $opencodePath -Raw | ConvertFrom-Json -AsHashtable + $opencodeOk = $o.ContainsKey("mcp") -and $o["mcp"].ContainsKey("sequential-thinking") + } catch {} + } + + if (-not ($claudeOk -and $codexOk -and $opencodeOk)) { + throw "Sequential-thinking MCP runtime config is incomplete" + } +} + +Require-Binary node +Require-Binary npx +Warm-Package + +if ($Check) { + Test-Configs + Write-Host "[mosaic-seq] sequential-thinking MCP is configured and available" + exit 0 +} + +Set-ClaudeConfig +Set-CodexConfig +Set-OpenCodeConfig +Write-Host "[mosaic-seq] sequential-thinking MCP configured for Claude, Codex, and OpenCode" diff --git a/packages/mosaic/framework/bin/mosaic-init.ps1 b/packages/mosaic/framework/bin/mosaic-init.ps1 new file mode 100644 index 0000000..07ddec7 --- /dev/null +++ b/packages/mosaic/framework/bin/mosaic-init.ps1 @@ -0,0 +1,144 @@ +# mosaic-init.ps1 — Interactive SOUL.md generator (Windows) +# +# Usage: +# mosaic-init.ps1 # Interactive mode +# mosaic-init.ps1 -Name "Jarvis" -Style direct # Flag overrides +$ErrorActionPreference = "Stop" + +param( + [string]$Name, + [string]$Role, + [ValidateSet("direct", "friendly", "formal")] + [string]$Style, + [string]$Accessibility, + [string]$Guardrails, + [switch]$NonInteractive, + [switch]$Help +) + +$MosaicHome = if ($env:MOSAIC_HOME) { $env:MOSAIC_HOME } else { Join-Path $env:USERPROFILE ".config\mosaic" } +$Template = Join-Path $MosaicHome "templates\SOUL.md.template" +$Output = Join-Path $MosaicHome "SOUL.md" + +if ($Help) { + Write-Host @" +Usage: mosaic-init.ps1 [-Name ] [-Role ] [-Style direct|friendly|formal] + [-Accessibility ] [-Guardrails ] [-NonInteractive] + +Generate ~/.config/mosaic/SOUL.md - the universal agent identity contract. +Interactive by default. Use flags to skip prompts. +"@ + exit 0 +} + +function Prompt-IfEmpty { + param([string]$Current, [string]$PromptText, [string]$Default = "") + + if ($Current) { return $Current } + + if ($NonInteractive) { + if ($Default) { return $Default } + Write-Host "[mosaic-init] ERROR: Value required in non-interactive mode: $PromptText" -ForegroundColor Red + exit 1 + } + + $display = if ($Default) { "$PromptText [$Default]" } else { $PromptText } + $value = Read-Host $display + if (-not $value -and $Default) { return $Default } + return $value +} + +Write-Host "[mosaic-init] Generating SOUL.md - your universal agent identity contract" +Write-Host "" + +$Name = Prompt-IfEmpty $Name "What name should agents use" "Assistant" +$Role = Prompt-IfEmpty $Role "Agent role description" "execution partner and visibility engine" + +if (-not $Style) { + if ($NonInteractive) { + $Style = "direct" + } + else { + Write-Host "" + Write-Host "Communication style:" + Write-Host " 1) direct - Concise, no fluff, actionable" + Write-Host " 2) friendly - Warm but efficient, conversational" + Write-Host " 3) formal - Professional, structured, thorough" + $choice = Read-Host "Choose [1/2/3] (default: 1)" + $Style = switch ($choice) { + "2" { "friendly" } + "3" { "formal" } + default { "direct" } + } + } +} + +$Accessibility = Prompt-IfEmpty $Accessibility "Accessibility preferences (or 'none')" "none" + +if (-not $Guardrails -and -not $NonInteractive) { + Write-Host "" + $Guardrails = Read-Host "Custom guardrails (optional, press Enter to skip)" +} + +# Build behavioral principles +$BehavioralPrinciples = switch ($Style) { + "direct" { + "1. Clarity over performance theater.`n2. Practical execution over abstract planning.`n3. Truthfulness over confidence: state uncertainty explicitly.`n4. Visible state over hidden assumptions." + } + "friendly" { + "1. Be helpful and approachable while staying efficient.`n2. Provide context and explain reasoning when helpful.`n3. Truthfulness over confidence: state uncertainty explicitly.`n4. Visible state over hidden assumptions." + } + "formal" { + "1. Maintain professional, structured communication.`n2. Provide thorough analysis with explicit tradeoffs.`n3. Truthfulness over confidence: state uncertainty explicitly.`n4. Document decisions and rationale clearly." + } +} + +if ($Accessibility -and $Accessibility -ne "none") { + $BehavioralPrinciples += "`n5. $Accessibility." +} + +# Build communication style +$CommunicationStyle = switch ($Style) { + "direct" { + "- Be direct, concise, and concrete.`n- Avoid fluff, hype, and anthropomorphic roleplay.`n- Do not simulate certainty when facts are missing.`n- Prefer actionable next steps and explicit tradeoffs." + } + "friendly" { + "- Be warm and conversational while staying focused.`n- Explain your reasoning when it helps the user.`n- Do not simulate certainty when facts are missing.`n- Prefer actionable next steps with clear context." + } + "formal" { + "- Use professional, structured language.`n- Provide thorough explanations with supporting detail.`n- Do not simulate certainty when facts are missing.`n- Present options with explicit tradeoffs and recommendations." + } +} + +# Format custom guardrails +$FormattedGuardrails = if ($Guardrails) { "- $Guardrails" } else { "" } + +# Verify template +if (-not (Test-Path $Template)) { + Write-Host "[mosaic-init] ERROR: Template not found: $Template" -ForegroundColor Red + exit 1 +} + +# Generate SOUL.md +$content = Get-Content $Template -Raw +$content = $content -replace '\{\{AGENT_NAME\}\}', $Name +$content = $content -replace '\{\{ROLE_DESCRIPTION\}\}', $Role +$content = $content -replace '\{\{BEHAVIORAL_PRINCIPLES\}\}', $BehavioralPrinciples +$content = $content -replace '\{\{COMMUNICATION_STYLE\}\}', $CommunicationStyle +$content = $content -replace '\{\{CUSTOM_GUARDRAILS\}\}', $FormattedGuardrails + +Set-Content -Path $Output -Value $content -Encoding UTF8 + +Write-Host "" +Write-Host "[mosaic-init] Generated: $Output" +Write-Host "[mosaic-init] Agent name: $Name" +Write-Host "[mosaic-init] Style: $Style" + +# Push to runtime adapters +$linkScript = Join-Path $MosaicHome "bin\mosaic-link-runtime-assets.ps1" +if (Test-Path $linkScript) { + Write-Host "[mosaic-init] Updating runtime adapters..." + & $linkScript +} + +Write-Host "[mosaic-init] Done. Launch with: mosaic claude" diff --git a/packages/mosaic/framework/bin/mosaic-link-runtime-assets.ps1 b/packages/mosaic/framework/bin/mosaic-link-runtime-assets.ps1 new file mode 100644 index 0000000..c83652c --- /dev/null +++ b/packages/mosaic/framework/bin/mosaic-link-runtime-assets.ps1 @@ -0,0 +1,111 @@ +# mosaic-link-runtime-assets.ps1 +# Syncs Mosaic runtime config files into agent runtime directories. +# PowerShell equivalent of mosaic-link-runtime-assets (bash). +$ErrorActionPreference = "Stop" + +$MosaicHome = if ($env:MOSAIC_HOME) { $env:MOSAIC_HOME } else { Join-Path $env:USERPROFILE ".config\mosaic" } +$BackupStamp = Get-Date -Format "yyyyMMddHHmmss" + +function Copy-FileManaged { + param([string]$Src, [string]$Dst) + + $parent = Split-Path $Dst -Parent + if (-not (Test-Path $parent)) { New-Item -ItemType Directory -Path $parent -Force | Out-Null } + + # Remove existing symlink/junction + $item = Get-Item $Dst -Force -ErrorAction SilentlyContinue + if ($item -and $item.Attributes -band [System.IO.FileAttributes]::ReparsePoint) { + Remove-Item $Dst -Force + } + + if (Test-Path $Dst) { + $srcHash = (Get-FileHash $Src -Algorithm SHA256).Hash + $dstHash = (Get-FileHash $Dst -Algorithm SHA256).Hash + if ($srcHash -eq $dstHash) { return } + Rename-Item $Dst "$Dst.mosaic-bak-$BackupStamp" + } + + Copy-Item $Src $Dst -Force +} + +function Remove-LegacyPath { + param([string]$Path) + + if (-not (Test-Path $Path)) { return } + + $item = Get-Item $Path -Force -ErrorAction SilentlyContinue + if ($item -and $item.Attributes -band [System.IO.FileAttributes]::ReparsePoint) { + Remove-Item $Path -Force + return + } + + if (Test-Path $Path -PathType Container) { + # Remove symlinks/junctions inside, then empty dirs + Get-ChildItem $Path -Recurse -Force | Where-Object { + $_.Attributes -band [System.IO.FileAttributes]::ReparsePoint + } | Remove-Item -Force + + Get-ChildItem $Path -Recurse -Directory -Force | + Sort-Object { $_.FullName.Length } -Descending | + Where-Object { (Get-ChildItem $_.FullName -Force | Measure-Object).Count -eq 0 } | + Remove-Item -Force + } +} + +# Remove legacy compatibility paths +$legacyPaths = @( + (Join-Path $env:USERPROFILE ".claude\agent-guides"), + (Join-Path $env:USERPROFILE ".claude\scripts\git"), + (Join-Path $env:USERPROFILE ".claude\scripts\codex"), + (Join-Path $env:USERPROFILE ".claude\scripts\bootstrap"), + (Join-Path $env:USERPROFILE ".claude\scripts\cicd"), + (Join-Path $env:USERPROFILE ".claude\scripts\portainer"), + (Join-Path $env:USERPROFILE ".claude\scripts\debug-hook.sh"), + (Join-Path $env:USERPROFILE ".claude\scripts\qa-hook-handler.sh"), + (Join-Path $env:USERPROFILE ".claude\scripts\qa-hook-stdin.sh"), + (Join-Path $env:USERPROFILE ".claude\scripts\qa-hook-wrapper.sh"), + (Join-Path $env:USERPROFILE ".claude\scripts\qa-queue-monitor.sh"), + (Join-Path $env:USERPROFILE ".claude\scripts\remediation-hook-handler.sh"), + (Join-Path $env:USERPROFILE ".claude\templates"), + (Join-Path $env:USERPROFILE ".claude\presets\domains"), + (Join-Path $env:USERPROFILE ".claude\presets\tech-stacks"), + (Join-Path $env:USERPROFILE ".claude\presets\workflows"), + (Join-Path $env:USERPROFILE ".claude\presets\jarvis-loop.json") +) + +foreach ($p in $legacyPaths) { + Remove-LegacyPath $p +} + +# Claude-specific runtime files (settings, hooks — CLAUDE.md is now a thin pointer) +$runtimeFiles = @("CLAUDE.md", "settings.json", "hooks-config.json", "context7-integration.md") +foreach ($rf in $runtimeFiles) { + $src = Join-Path $MosaicHome "runtime\claude\$rf" + if (-not (Test-Path $src)) { continue } + $dst = Join-Path $env:USERPROFILE ".claude\$rf" + Copy-FileManaged $src $dst +} + +# OpenCode runtime adapter +$opencodeSrc = Join-Path $MosaicHome "runtime\opencode\AGENTS.md" +if (Test-Path $opencodeSrc) { + $opencodeDst = Join-Path $env:USERPROFILE ".config\opencode\AGENTS.md" + Copy-FileManaged $opencodeSrc $opencodeDst +} + +# Codex runtime adapter +$codexSrc = Join-Path $MosaicHome "runtime\codex\instructions.md" +if (Test-Path $codexSrc) { + $codexDir = Join-Path $env:USERPROFILE ".codex" + if (-not (Test-Path $codexDir)) { New-Item -ItemType Directory -Path $codexDir -Force | Out-Null } + $codexDst = Join-Path $codexDir "instructions.md" + Copy-FileManaged $codexSrc $codexDst +} + +$seqScript = Join-Path $MosaicHome "bin\mosaic-ensure-sequential-thinking.ps1" +if (Test-Path $seqScript) { + & $seqScript +} + +Write-Host "[mosaic-link] Runtime assets synced (non-symlink mode)" +Write-Host "[mosaic-link] Canonical source: $MosaicHome" diff --git a/packages/mosaic/framework/bin/mosaic-migrate-local-skills.ps1 b/packages/mosaic/framework/bin/mosaic-migrate-local-skills.ps1 new file mode 100644 index 0000000..9da85e9 --- /dev/null +++ b/packages/mosaic/framework/bin/mosaic-migrate-local-skills.ps1 @@ -0,0 +1,90 @@ +# mosaic-migrate-local-skills.ps1 +# Migrates runtime-local skill directories to Mosaic-managed junctions. +# Uses directory junctions (no elevation required) with fallback to copies. +# PowerShell equivalent of mosaic-migrate-local-skills (bash). +$ErrorActionPreference = "Stop" + +param( + [switch]$Apply, + [switch]$Help +) + +$MosaicHome = if ($env:MOSAIC_HOME) { $env:MOSAIC_HOME } else { Join-Path $env:USERPROFILE ".config\mosaic" } +$LocalSkillsDir = Join-Path $MosaicHome "skills-local" + +if ($Help) { + Write-Host @" +Usage: mosaic-migrate-local-skills.ps1 [-Apply] [-Help] + +Migrate runtime-local skill directories (e.g. ~/.claude/skills/jarvis) to +Mosaic-managed skills by replacing local directories with junctions to +~/.config/mosaic/skills-local. + +Default mode is dry-run. +"@ + exit 0 +} + +if (-not (Test-Path $LocalSkillsDir)) { + Write-Host "[mosaic-local-skills] Missing local skills dir: $LocalSkillsDir" -ForegroundColor Red + exit 1 +} + +$skillRoots = @( + (Join-Path $env:USERPROFILE ".claude\skills"), + (Join-Path $env:USERPROFILE ".codex\skills"), + (Join-Path $env:USERPROFILE ".config\opencode\skills") +) + +$count = 0 + +Get-ChildItem $LocalSkillsDir -Directory | ForEach-Object { + $name = $_.Name + $src = $_.FullName + + foreach ($root in $skillRoots) { + if (-not (Test-Path $root)) { continue } + $target = Join-Path $root $name + + # Already a junction/symlink — check if it points to the right place + $existing = Get-Item $target -Force -ErrorAction SilentlyContinue + if ($existing -and ($existing.Attributes -band [System.IO.FileAttributes]::ReparsePoint)) { + $currentTarget = $existing.Target + if ($currentTarget -and ($currentTarget -eq $src -or (Resolve-Path $currentTarget -ErrorAction SilentlyContinue).Path -eq (Resolve-Path $src -ErrorAction SilentlyContinue).Path)) { + continue + } + } + + # Only migrate local directories containing SKILL.md + if ((Test-Path $target -PathType Container) -and + (Test-Path (Join-Path $target "SKILL.md")) -and + -not ($existing -and ($existing.Attributes -band [System.IO.FileAttributes]::ReparsePoint))) { + + $count++ + if ($Apply) { + $stamp = Get-Date -Format "yyyyMMddHHmmss" + Rename-Item $target "$target.mosaic-bak-$stamp" + try { + New-Item -ItemType Junction -Path $target -Target $src -ErrorAction Stop | Out-Null + Write-Host "[mosaic-local-skills] migrated: $target -> $src" + } + catch { + Write-Host "[mosaic-local-skills] Junction failed for $name, falling back to copy" + Copy-Item $src $target -Recurse -Force + Write-Host "[mosaic-local-skills] copied: $target <- $src" + } + } + else { + Write-Host "[mosaic-local-skills] would migrate: $target -> $src" + } + } + } +} + +if ($Apply) { + Write-Host "[mosaic-local-skills] complete: migrated=$count" +} +else { + Write-Host "[mosaic-local-skills] dry-run: migratable=$count" + Write-Host "[mosaic-local-skills] re-run with -Apply to migrate" +} diff --git a/packages/mosaic/framework/bin/mosaic-release-upgrade.ps1 b/packages/mosaic/framework/bin/mosaic-release-upgrade.ps1 new file mode 100644 index 0000000..016ed7a --- /dev/null +++ b/packages/mosaic/framework/bin/mosaic-release-upgrade.ps1 @@ -0,0 +1,65 @@ +# mosaic-release-upgrade.ps1 — Upgrade installed Mosaic framework release (Windows) +# +# Usage: +# mosaic-release-upgrade.ps1 +# mosaic-release-upgrade.ps1 -Ref main -Keep +# mosaic-release-upgrade.ps1 -Ref v0.2.0 -Overwrite -Yes +# +param( + [string]$Ref = $(if ($env:MOSAIC_BOOTSTRAP_REF) { $env:MOSAIC_BOOTSTRAP_REF } else { "main" }), + [switch]$Keep, + [switch]$Overwrite, + [switch]$Yes, + [switch]$DryRun +) + +$ErrorActionPreference = "Stop" + +$MosaicHome = if ($env:MOSAIC_HOME) { $env:MOSAIC_HOME } else { Join-Path $env:USERPROFILE ".config\mosaic" } +$RemoteInstallerUrl = if ($env:MOSAIC_REMOTE_INSTALL_URL) { + $env:MOSAIC_REMOTE_INSTALL_URL +} else { + "https://git.mosaicstack.dev/mosaic/bootstrap/raw/branch/main/remote-install.ps1" +} + +$installMode = if ($Overwrite) { "overwrite" } elseif ($Keep) { "keep" } elseif ($env:MOSAIC_INSTALL_MODE) { $env:MOSAIC_INSTALL_MODE } else { "keep" } +if ($installMode -notin @("keep", "overwrite")) { + Write-Host "[mosaic-release-upgrade] Invalid install mode: $installMode" -ForegroundColor Red + exit 1 +} + +$currentVersion = "unknown" +$mosaicCmd = Join-Path $MosaicHome "bin\mosaic.ps1" +if (Test-Path $mosaicCmd) { + try { + $currentVersion = (& $mosaicCmd --version) -replace '^mosaic\s+', '' + } + catch { + $currentVersion = "unknown" + } +} + +Write-Host "[mosaic-release-upgrade] Current version: $currentVersion" +Write-Host "[mosaic-release-upgrade] Target ref: $Ref" +Write-Host "[mosaic-release-upgrade] Install mode: $installMode" +Write-Host "[mosaic-release-upgrade] Installer URL: $RemoteInstallerUrl" + +if ($DryRun) { + Write-Host "[mosaic-release-upgrade] Dry run: no changes applied." + exit 0 +} + +if (-not $Yes) { + $confirmation = Read-Host "Proceed with Mosaic release upgrade? [y/N]" + if ($confirmation -notin @("y", "Y", "yes", "YES")) { + Write-Host "[mosaic-release-upgrade] Aborted." + exit 1 + } +} + +$env:MOSAIC_BOOTSTRAP_REF = $Ref +$env:MOSAIC_INSTALL_MODE = $installMode +$env:MOSAIC_HOME = $MosaicHome + +Invoke-RestMethod -Uri $RemoteInstallerUrl | Invoke-Expression + diff --git a/packages/mosaic/framework/bin/mosaic-sync-skills.ps1 b/packages/mosaic/framework/bin/mosaic-sync-skills.ps1 new file mode 100644 index 0000000..c2a8fd5 --- /dev/null +++ b/packages/mosaic/framework/bin/mosaic-sync-skills.ps1 @@ -0,0 +1,126 @@ +# mosaic-sync-skills.ps1 +# Syncs canonical skills and links them into agent runtime skill directories. +# Uses directory junctions (no elevation required) with fallback to copies. +# PowerShell equivalent of mosaic-sync-skills (bash). +$ErrorActionPreference = "Stop" + +param( + [switch]$LinkOnly, + [switch]$NoLink, + [switch]$Help +) + +$MosaicHome = if ($env:MOSAIC_HOME) { $env:MOSAIC_HOME } else { Join-Path $env:USERPROFILE ".config\mosaic" } +$SkillsRepoUrl = if ($env:MOSAIC_SKILLS_REPO_URL) { $env:MOSAIC_SKILLS_REPO_URL } else { "https://git.mosaicstack.dev/mosaic/agent-skills.git" } +$SkillsRepoDir = if ($env:MOSAIC_SKILLS_REPO_DIR) { $env:MOSAIC_SKILLS_REPO_DIR } else { Join-Path $MosaicHome "sources\agent-skills" } +$MosaicSkillsDir = Join-Path $MosaicHome "skills" +$MosaicLocalSkillsDir = Join-Path $MosaicHome "skills-local" + +if ($Help) { + Write-Host @" +Usage: mosaic-sync-skills.ps1 [-LinkOnly] [-NoLink] [-Help] + +Sync canonical skills into ~/.config/mosaic/skills and link all Mosaic skills +into runtime skill directories using directory junctions. + +Options: + -LinkOnly Skip git clone/pull and only relink + -NoLink Sync canonical skills but do not update runtime links + -Help Show help +"@ + exit 0 +} + +foreach ($d in @($MosaicHome, $MosaicSkillsDir, $MosaicLocalSkillsDir)) { + if (-not (Test-Path $d)) { New-Item -ItemType Directory -Path $d -Force | Out-Null } +} + +# Fetch skills from git +if (-not $LinkOnly) { + if (Test-Path (Join-Path $SkillsRepoDir ".git")) { + Write-Host "[mosaic-skills] Updating skills source: $SkillsRepoDir" + git -C $SkillsRepoDir pull --rebase + } + else { + Write-Host "[mosaic-skills] Cloning skills source to: $SkillsRepoDir" + $parentDir = Split-Path $SkillsRepoDir -Parent + if (-not (Test-Path $parentDir)) { New-Item -ItemType Directory -Path $parentDir -Force | Out-Null } + git clone $SkillsRepoUrl $SkillsRepoDir + } + + $sourceSkillsDir = Join-Path $SkillsRepoDir "skills" + if (-not (Test-Path $sourceSkillsDir)) { + Write-Host "[mosaic-skills] Missing source skills dir: $sourceSkillsDir" -ForegroundColor Red + exit 1 + } + + # Sync: remove old, copy new + if (Test-Path $MosaicSkillsDir) { + Get-ChildItem $MosaicSkillsDir | Remove-Item -Recurse -Force + } + Copy-Item "$sourceSkillsDir\*" $MosaicSkillsDir -Recurse -Force +} + +if (-not (Test-Path $MosaicSkillsDir)) { + Write-Host "[mosaic-skills] Canonical skills dir missing: $MosaicSkillsDir" -ForegroundColor Red + exit 1 +} + +if ($NoLink) { + Write-Host "[mosaic-skills] Canonical sync completed (link update skipped)" + exit 0 +} + +function Link-SkillIntoTarget { + param([string]$SkillPath, [string]$TargetDir) + + $name = Split-Path $SkillPath -Leaf + if ($name.StartsWith(".")) { return } + + $linkPath = Join-Path $TargetDir $name + + # Already a junction/symlink — recreate + $existing = Get-Item $linkPath -Force -ErrorAction SilentlyContinue + if ($existing -and ($existing.Attributes -band [System.IO.FileAttributes]::ReparsePoint)) { + Remove-Item $linkPath -Force + } + elseif ($existing) { + Write-Host "[mosaic-skills] Preserve existing runtime-specific entry: $linkPath" + return + } + + # Try junction first, fall back to copy + try { + New-Item -ItemType Junction -Path $linkPath -Target $SkillPath -ErrorAction Stop | Out-Null + } + catch { + Write-Host "[mosaic-skills] Junction failed for $name, falling back to copy" + Copy-Item $SkillPath $linkPath -Recurse -Force + } +} + +$linkTargets = @( + (Join-Path $env:USERPROFILE ".claude\skills"), + (Join-Path $env:USERPROFILE ".codex\skills"), + (Join-Path $env:USERPROFILE ".config\opencode\skills") +) + +foreach ($target in $linkTargets) { + if (-not (Test-Path $target)) { New-Item -ItemType Directory -Path $target -Force | Out-Null } + + # Link canonical skills + Get-ChildItem $MosaicSkillsDir -Directory | ForEach-Object { + Link-SkillIntoTarget $_.FullName $target + } + + # Link local skills + if (Test-Path $MosaicLocalSkillsDir) { + Get-ChildItem $MosaicLocalSkillsDir -Directory | ForEach-Object { + Link-SkillIntoTarget $_.FullName $target + } + } + + Write-Host "[mosaic-skills] Linked skills into: $target" +} + +Write-Host "[mosaic-skills] Complete" diff --git a/packages/mosaic/framework/bin/mosaic.ps1 b/packages/mosaic/framework/bin/mosaic.ps1 new file mode 100644 index 0000000..fcc0bfb --- /dev/null +++ b/packages/mosaic/framework/bin/mosaic.ps1 @@ -0,0 +1,437 @@ +# mosaic.ps1 — Unified agent launcher and management CLI (Windows) +# +# AGENTS.md is the global policy source for all agent sessions. +# The launcher injects a composed runtime contract (AGENTS + runtime reference). +# +# Usage: +# mosaic claude [args...] Launch Claude Code with runtime contract injected +# mosaic opencode [args...] Launch OpenCode with runtime contract injected +# mosaic codex [args...] Launch Codex with runtime contract injected +# mosaic yolo [args...] Launch runtime in dangerous-permissions mode +# mosaic --yolo [args...] Alias for yolo +# mosaic init [args...] Generate SOUL.md interactively +# mosaic doctor [args...] Health audit +# mosaic sync [args...] Sync skills +$ErrorActionPreference = "Stop" + +$MosaicHome = if ($env:MOSAIC_HOME) { $env:MOSAIC_HOME } else { Join-Path $env:USERPROFILE ".config\mosaic" } +$Version = "0.1.0" + +function Show-Usage { + Write-Host @" +mosaic $Version - Unified agent launcher + +Usage: mosaic [args...] + +Agent Launchers: + claude [args...] Launch Claude Code with runtime contract injected + opencode [args...] Launch OpenCode with runtime contract injected + codex [args...] Launch Codex with runtime contract injected + yolo [args...] Dangerous mode for claude|codex|opencode + --yolo [args...] Alias for yolo + +Management: + init [args...] Generate SOUL.md (agent identity contract) + doctor [args...] Audit runtime state and detect drift + sync [args...] Sync skills from canonical source + bootstrap Bootstrap a repo with Mosaic standards + upgrade [mode] [args] Upgrade release (default) or project files + upgrade check Check release upgrade status (no changes) + release-upgrade [...] Upgrade installed Mosaic release + project-upgrade [...] Clean up stale SOUL.md/CLAUDE.md in a project + +Options: + -h, --help Show this help + -v, --version Show version +"@ +} + +function Assert-MosaicHome { + if (-not (Test-Path $MosaicHome)) { + Write-Host "[mosaic] ERROR: ~/.config/mosaic not found." -ForegroundColor Red + Write-Host "[mosaic] Install with: irm https://git.mosaicstack.dev/mosaic/bootstrap/raw/branch/main/remote-install.ps1 | iex" + exit 1 + } +} + +function Assert-AgentsMd { + $agentsPath = Join-Path $MosaicHome "AGENTS.md" + if (-not (Test-Path $agentsPath)) { + Write-Host "[mosaic] ERROR: ~/.config/mosaic/AGENTS.md not found." -ForegroundColor Red + Write-Host "[mosaic] Re-run the installer." + exit 1 + } +} + +function Assert-Soul { + $soulPath = Join-Path $MosaicHome "SOUL.md" + if (-not (Test-Path $soulPath)) { + Write-Host "[mosaic] SOUL.md not found. Running mosaic init..." + & (Join-Path $MosaicHome "bin\mosaic-init.ps1") + } +} + +function Assert-Runtime { + param([string]$Cmd) + if (-not (Get-Command $Cmd -ErrorAction SilentlyContinue)) { + Write-Host "[mosaic] ERROR: '$Cmd' not found in PATH." -ForegroundColor Red + Write-Host "[mosaic] Install $Cmd before launching." + exit 1 + } +} + +function Assert-SequentialThinking { + $checker = Join-Path $MosaicHome "bin\mosaic-ensure-sequential-thinking.ps1" + if (-not (Test-Path $checker)) { + Write-Host "[mosaic] ERROR: sequential-thinking checker missing: $checker" -ForegroundColor Red + exit 1 + } + try { + & $checker -Check *>$null + } + catch { + Write-Host "[mosaic] ERROR: sequential-thinking MCP is required but not configured." -ForegroundColor Red + Write-Host "[mosaic] Run: $checker" + exit 1 + } +} + +function Get-ActiveMission { + $missionFile = Join-Path (Get-Location) ".mosaic\orchestrator\mission.json" + if (-not (Test-Path $missionFile)) { + return $null + } + + try { + $mission = Get-Content $missionFile -Raw | ConvertFrom-Json + } + catch { + return $null + } + + $status = [string]$mission.status + if ([string]::IsNullOrWhiteSpace($status)) { + $status = "inactive" + } + if ($status -ne "active" -and $status -ne "paused") { + return $null + } + + $name = [string]$mission.name + if ([string]::IsNullOrWhiteSpace($name)) { + $name = "unnamed" + } + + $id = [string]$mission.mission_id + if ([string]::IsNullOrWhiteSpace($id)) { + $id = "" + } + + $milestones = @($mission.milestones) + $milestoneCount = $milestones.Count + $milestoneCompleted = @($milestones | Where-Object { $_.status -eq "completed" }).Count + + return [PSCustomObject]@{ + Name = $name + Id = $id + Status = $status + MilestoneCount = $milestoneCount + MilestoneCompleted = $milestoneCompleted + } +} + +function Get-MissionContractBlock { + $mission = Get-ActiveMission + if ($null -eq $mission) { + return "" + } + + return @" +# ACTIVE MISSION — HARD GATE (Read Before Anything Else) + +An active orchestration mission exists in this project. This is a BLOCKING requirement. + +**Mission:** $($mission.Name) +**ID:** $($mission.Id) +**Status:** $($mission.Status) +**Milestones:** $($mission.MilestoneCompleted) / $($mission.MilestoneCount) completed + +## MANDATORY — Before ANY Response to the User + +You MUST complete these steps before responding to any user message, including simple greetings: + +1. Read `~/.config/mosaic/guides/ORCHESTRATOR-PROTOCOL.md` (mission lifecycle protocol) +2. Read `docs/MISSION-MANIFEST.md` for full mission scope, milestones, and success criteria +3. Read the latest scratchpad in `docs/scratchpads/` for session history, decisions, and corrections +4. Read `docs/TASKS.md` for current task state (what is done, what is next) +5. After reading all four, acknowledge the mission state to the user before proceeding + +If the user gives a task, execute it within the mission context. If no task is given, present mission status and ask how to proceed. +"@ +} + +function Get-MissionPrompt { + $mission = Get-ActiveMission + if ($null -eq $mission) { + return "" + } + return "Active mission detected: $($mission.Name). Read the mission state files and report status." +} + +function Get-RuntimePrompt { + param( + [ValidateSet("claude", "codex", "opencode")] + [string]$Runtime + ) + + $runtimeFile = switch ($Runtime) { + "claude" { Join-Path $MosaicHome "runtime\claude\RUNTIME.md" } + "codex" { Join-Path $MosaicHome "runtime\codex\RUNTIME.md" } + "opencode" { Join-Path $MosaicHome "runtime\opencode\RUNTIME.md" } + } + + if (-not (Test-Path $runtimeFile)) { + Write-Host "[mosaic] ERROR: runtime contract not found: $runtimeFile" -ForegroundColor Red + exit 1 + } + + $launcherContract = @' +# Mosaic Launcher Runtime Contract (Hard Gate) + +This contract is injected by `mosaic` launch and is mandatory. + +First assistant response MUST start with exactly one mode declaration line: +1. Orchestration mission: `Now initiating Orchestrator mode...` +2. Implementation mission: `Now initiating Delivery mode...` +3. Review-only mission: `Now initiating Review mode...` + +No tool call or implementation step may occur before that first line. + +Mosaic hard gates OVERRIDE runtime-default caution for routine delivery operations. +For required push/merge/issue-close/release actions, execute without routine confirmation prompts. + +'@ + + $missionBlock = Get-MissionContractBlock + $agentsContent = Get-Content (Join-Path $MosaicHome "AGENTS.md") -Raw + $runtimeContent = Get-Content $runtimeFile -Raw + + if (-not [string]::IsNullOrWhiteSpace($missionBlock)) { + return "$missionBlock`n`n$launcherContract`n$agentsContent`n`n# Runtime-Specific Contract`n`n$runtimeContent" + } + + return "$launcherContract`n$agentsContent`n`n# Runtime-Specific Contract`n`n$runtimeContent" +} + +function Ensure-RuntimeConfig { + param( + [ValidateSet("claude", "codex", "opencode")] + [string]$Runtime, + [string]$Dst + ) + + $parent = Split-Path $Dst -Parent + if (-not (Test-Path $parent)) { New-Item -ItemType Directory -Path $parent -Force | Out-Null } + + $runtimePrompt = Get-RuntimePrompt -Runtime $Runtime + $tmp = [System.IO.Path]::GetTempFileName() + Set-Content -Path $tmp -Value $runtimePrompt -Encoding UTF8 -NoNewline + + $srcHash = (Get-FileHash $tmp -Algorithm SHA256).Hash + $dstHash = if (Test-Path $Dst) { (Get-FileHash $Dst -Algorithm SHA256).Hash } else { "" } + if ($srcHash -ne $dstHash) { + Copy-Item $tmp $Dst -Force + Remove-Item $tmp -Force + } + else { + Remove-Item $tmp -Force + } +} + +function Invoke-Yolo { + param([string[]]$YoloArgs) + + if ($YoloArgs.Count -lt 1) { + Write-Host "[mosaic] ERROR: yolo requires a runtime (claude|codex|opencode)." -ForegroundColor Red + Write-Host "[mosaic] Example: mosaic yolo claude" + exit 1 + } + + $runtime = $YoloArgs[0] + $tail = if ($YoloArgs.Count -gt 1) { @($YoloArgs[1..($YoloArgs.Count - 1)]) } else { @() } + + switch ($runtime) { + "claude" { + Assert-MosaicHome + Assert-AgentsMd + Assert-Soul + Assert-Runtime "claude" + Assert-SequentialThinking + $agentsContent = Get-RuntimePrompt -Runtime "claude" + Write-Host "[mosaic] Launching Claude Code in YOLO mode (dangerous permissions enabled)..." + & claude --dangerously-skip-permissions --append-system-prompt $agentsContent @tail + return + } + "codex" { + Assert-MosaicHome + Assert-AgentsMd + Assert-Soul + Assert-Runtime "codex" + Assert-SequentialThinking + Ensure-RuntimeConfig -Runtime "codex" -Dst (Join-Path $env:USERPROFILE ".codex\instructions.md") + $missionPrompt = Get-MissionPrompt + if (-not [string]::IsNullOrWhiteSpace($missionPrompt) -and $tail.Count -eq 0) { + Write-Host "[mosaic] Launching Codex in YOLO mode (active mission detected)..." + & codex --dangerously-bypass-approvals-and-sandbox $missionPrompt + } + else { + Write-Host "[mosaic] Launching Codex in YOLO mode (dangerous permissions enabled)..." + & codex --dangerously-bypass-approvals-and-sandbox @tail + } + return + } + "opencode" { + Assert-MosaicHome + Assert-AgentsMd + Assert-Soul + Assert-Runtime "opencode" + Assert-SequentialThinking + Ensure-RuntimeConfig -Runtime "opencode" -Dst (Join-Path $env:USERPROFILE ".config\opencode\AGENTS.md") + Write-Host "[mosaic] Launching OpenCode in YOLO mode..." + & opencode @tail + return + } + default { + Write-Host "[mosaic] ERROR: Unsupported yolo runtime '$runtime'. Use claude|codex|opencode." -ForegroundColor Red + exit 1 + } + } +} + +if ($args.Count -eq 0) { + Show-Usage + exit 0 +} + +$command = $args[0] +$remaining = if ($args.Count -gt 1) { @($args[1..($args.Count - 1)]) } else { @() } + +switch ($command) { + "claude" { + Assert-MosaicHome + Assert-AgentsMd + Assert-Soul + Assert-Runtime "claude" + Assert-SequentialThinking + # Claude supports --append-system-prompt for direct injection + $agentsContent = Get-RuntimePrompt -Runtime "claude" + Write-Host "[mosaic] Launching Claude Code..." + & claude --append-system-prompt $agentsContent @remaining + } + "opencode" { + Assert-MosaicHome + Assert-AgentsMd + Assert-Soul + Assert-Runtime "opencode" + Assert-SequentialThinking + # OpenCode reads from ~/.config/opencode/AGENTS.md + Ensure-RuntimeConfig -Runtime "opencode" -Dst (Join-Path $env:USERPROFILE ".config\opencode\AGENTS.md") + Write-Host "[mosaic] Launching OpenCode..." + & opencode @remaining + } + "codex" { + Assert-MosaicHome + Assert-AgentsMd + Assert-Soul + Assert-Runtime "codex" + Assert-SequentialThinking + # Codex reads from ~/.codex/instructions.md + Ensure-RuntimeConfig -Runtime "codex" -Dst (Join-Path $env:USERPROFILE ".codex\instructions.md") + $missionPrompt = Get-MissionPrompt + if (-not [string]::IsNullOrWhiteSpace($missionPrompt) -and $remaining.Count -eq 0) { + Write-Host "[mosaic] Launching Codex (active mission detected)..." + & codex $missionPrompt + } + else { + Write-Host "[mosaic] Launching Codex..." + & codex @remaining + } + } + "yolo" { + Invoke-Yolo -YoloArgs $remaining + } + "--yolo" { + Invoke-Yolo -YoloArgs $remaining + } + "init" { + Assert-MosaicHome + & (Join-Path $MosaicHome "bin\mosaic-init.ps1") @remaining + } + "doctor" { + Assert-MosaicHome + & (Join-Path $MosaicHome "bin\mosaic-doctor.ps1") @remaining + } + "sync" { + Assert-MosaicHome + & (Join-Path $MosaicHome "bin\mosaic-sync-skills.ps1") @remaining + } + "bootstrap" { + Assert-MosaicHome + Write-Host "[mosaic] NOTE: mosaic-bootstrap-repo requires bash. Use Git Bash or WSL." -ForegroundColor Yellow + & (Join-Path $MosaicHome "bin\mosaic-bootstrap-repo") @remaining + } + "upgrade" { + Assert-MosaicHome + if ($remaining.Count -eq 0) { + & (Join-Path $MosaicHome "bin\mosaic-release-upgrade.ps1") + break + } + + $mode = $remaining[0] + $tail = if ($remaining.Count -gt 1) { $remaining[1..($remaining.Count - 1)] } else { @() } + + switch -Regex ($mode) { + "^release$" { + & (Join-Path $MosaicHome "bin\mosaic-release-upgrade.ps1") @tail + } + "^check$" { + & (Join-Path $MosaicHome "bin\mosaic-release-upgrade.ps1") -DryRun @tail + } + "^project$" { + Write-Host "[mosaic] NOTE: mosaic-upgrade requires bash. Use Git Bash or WSL." -ForegroundColor Yellow + & (Join-Path $MosaicHome "bin\mosaic-upgrade") @tail + } + "^(--all|--root)$" { + Write-Host "[mosaic] NOTE: mosaic-upgrade requires bash. Use Git Bash or WSL." -ForegroundColor Yellow + & (Join-Path $MosaicHome "bin\mosaic-upgrade") @remaining + } + "^(--dry-run|--ref|--keep|--overwrite|-y|--yes)$" { + & (Join-Path $MosaicHome "bin\mosaic-release-upgrade.ps1") @remaining + } + "^-.*" { + & (Join-Path $MosaicHome "bin\mosaic-release-upgrade.ps1") @remaining + } + default { + Write-Host "[mosaic] NOTE: treating positional argument as project path." -ForegroundColor Yellow + Write-Host "[mosaic] NOTE: mosaic-upgrade requires bash. Use Git Bash or WSL." -ForegroundColor Yellow + & (Join-Path $MosaicHome "bin\mosaic-upgrade") @remaining + } + } + } + "release-upgrade" { + Assert-MosaicHome + & (Join-Path $MosaicHome "bin\mosaic-release-upgrade.ps1") @remaining + } + "project-upgrade" { + Assert-MosaicHome + Write-Host "[mosaic] NOTE: mosaic-upgrade requires bash. Use Git Bash or WSL." -ForegroundColor Yellow + & (Join-Path $MosaicHome "bin\mosaic-upgrade") @remaining + } + { $_ -in "help", "-h", "--help" } { Show-Usage } + { $_ -in "version", "-v", "--version" } { Write-Host "mosaic $Version" } + default { + Write-Host "[mosaic] Unknown command: $command" -ForegroundColor Red + Write-Host "[mosaic] Run 'mosaic --help' for usage." + exit 1 + } +} diff --git a/packages/mosaic/framework/defaults/AUDIT-2026-02-17-framework-consistency.md b/packages/mosaic/framework/defaults/AUDIT-2026-02-17-framework-consistency.md new file mode 100644 index 0000000..538aa53 --- /dev/null +++ b/packages/mosaic/framework/defaults/AUDIT-2026-02-17-framework-consistency.md @@ -0,0 +1,139 @@ +# Mosaic Framework Consistency Audit + +Date: 2026-02-17 +Scope: + +- `~/src/mosaic-bootstrap` +- `~/src/agent-skills` +- Installed runtime check: `~/.config/mosaic` + +## Findings + +| ID | Severity | Finding | Status | +| ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------ | ---------- | +| MF-001 | High | QA rails scripts referenced invalid paths (missing `/qa/` segment) for `qa-hook-handler.sh` calls. | Remediated | +| MF-002 | Medium | Conditional-loading detection was biased to legacy `agent-guides` markers and missed Mosaic `guides`/`Conditional Context` phrasing. | Remediated | +| MF-003 | Medium | Framework spec text still described detection as `agent-guides`-based only. | Remediated | +| MF-004 | Medium | Runtime Claude overlay hardcoded skill count, which drifts over time. | Remediated | +| MF-005 | High | `pr-reviewer` skill used legacy `~/.claude/scripts/git` and `~/.claude/skills` paths. | Remediated | +| MF-006 | Medium | `using-git-worktrees` prioritized `CLAUDE.md` only and ignored Mosaic-first docs (`AGENTS.md`/`SOUL.md`). | Remediated | +| MF-007 | Medium | Marketing skills used `.claude/product-marketing-context.md` instead of Mosaic repo-local path. | Remediated | +| MF-008 | Low | `agent-skills/README.md` manual install/adaptation guidance referenced legacy Claude-only paths. | Remediated | +| MF-009 | Low | Historical creation-log contained stale Claude-specific path examples. | Remediated | + +## Remediation Details + +### MF-001 (QA rails path correction) + +Updated: + +- `tools/qa/qa-hook-wrapper.sh` +- `tools/qa/qa-hook-stdin.sh` +- `tools/qa/qa-hook-handler.sh` +- `tools/qa/remediation-hook-handler.sh` +- `tools/qa/qa-queue-monitor.sh` + +Change: + +- Standardized handler paths to `~/.config/mosaic/tools/qa/...`. + +### MF-002 + MF-003 (conditional loading/context detection) + +Updated: + +- `tools/bootstrap/agent-lint.sh` +- `tools/bootstrap/agent-upgrade.sh` +- `templates/agent/SPEC.md` + +Change: + +- Detection now recognizes both legacy and Mosaic patterns: + - `agent-guides` (legacy compatibility) + - `~/.config/mosaic/guides` + - `Conditional Loading` + - `Conditional Context` + +### MF-004 (runtime overlay drift-proofing) + +Updated: + +- `runtime/claude/CLAUDE.md` + +Change: + +- Removed hardcoded skills count text. + +### MF-005 (pr-reviewer skill path migration) + +Updated: + +- `skills/pr-reviewer/SKILL.md` + +Change: + +- Replaced all `~/.claude/scripts/git/...` with `~/.config/mosaic/tools/git/...`. +- Replaced `~/.claude/skills/...` with `~/.config/mosaic/skills/...`. + +### MF-006 (worktree skill docs hierarchy) + +Updated: + +- `skills/using-git-worktrees/SKILL.md` + +Change: + +- Replaced CLAUDE-only checks with `AGENTS.md` / `SOUL.md` (fallback: `CLAUDE.md`). + +### MF-007 (marketing context path migration) + +Updated: + +- `skills/product-marketing-context/SKILL.md` +- All skills referencing product-marketing-context path (bulk update) + +Change: + +- Standardized context path to `.mosaic/product-marketing-context.md`. + +### MF-008 + MF-009 (repo guidance cleanup) + +Updated: + +- `README.md` (agent-skills) +- `skills/writing-skills/SKILL.md` +- `skills/writing-skills/examples/CLAUDE_MD_TESTING.md` +- `skills/systematic-debugging/CREATION-LOG.md` + +Change: + +- Standardized references to `~/.config/mosaic/skills`. +- Removed stale Claude-only wording. + +## Residual Legacy References (Intentional) + +The following legacy references remain in `mosaic-bootstrap` by design and are not inconsistencies: + +- Runtime compatibility tooling for Claude home directory management: + - `bin/mosaic-link-runtime-assets` + - `bin/mosaic-clean-runtime` + - `bin/mosaic-doctor` + - `bin/mosaic-sync-skills` + - `bin/mosaic-migrate-local-skills` +- Documentation explaining Claude runtime overlays: + - `README.md` + - `profiles/README.md` + - `adapters/claude.md` + - `runtime/claude/settings-overlays/jarvis-loop.json` + +These are required to support existing Claude runtime integration while keeping Mosaic as canonical source. + +## Verification + +Executed checks: + +- `rg -n "~/.claude|\\.claude/|agent-guides" ~/src/agent-skills -S` + - Result: no matches after remediation. +- `rg -n "~/.config/mosaic/tools/(qa-hook|remediation-hook|qa-queue-monitor)" ~/src/mosaic-bootstrap -S` + - Result: no invalid old-style QA rail paths remain. +- Installed runtime validation: + - `~/.config/mosaic` contains `tools/git`, `tools/portainer`, `tools/cicd`, `skills`, and `bin` tooling. diff --git a/packages/mosaic/framework/guides/AUTHENTICATION.md b/packages/mosaic/framework/guides/AUTHENTICATION.md new file mode 100644 index 0000000..da822e2 --- /dev/null +++ b/packages/mosaic/framework/guides/AUTHENTICATION.md @@ -0,0 +1,193 @@ +# Authentication & Authorization Guide + +## Before Starting + +1. Check assigned issue: `~/.config/mosaic/tools/git/issue-list.sh -a @me` +2. Review existing auth implementation in codebase +3. Review Vault secrets structure: `docs/vault-secrets-structure.md` + +## Authentication Patterns + +### JWT (JSON Web Tokens) + +``` +Vault Path: secret-{env}/backend-api/jwt/signing-key +Fields: key, algorithm, expiry_seconds +``` + +**Best Practices:** + +- Use RS256 or ES256 (asymmetric) for distributed systems +- Use HS256 (symmetric) only for single-service auth +- Set reasonable expiry (15min-1hr for access tokens) +- Include minimal claims (sub, exp, iat, roles) +- Never store sensitive data in JWT payload + +### Session-Based + +``` +Vault Path: secret-{env}/{service}/session/secret +Fields: secret, cookie_name, max_age +``` + +**Best Practices:** + +- Use secure, httpOnly, sameSite cookies +- Regenerate session ID on privilege change +- Implement session timeout +- Store sessions server-side (Redis/database) + +### OAuth2/OIDC + +``` +Vault Paths: +- secret-{env}/{service}/oauth/{provider}/client_id +- secret-{env}/{service}/oauth/{provider}/client_secret +``` + +**Best Practices:** + +- Use PKCE for public clients +- Validate state parameter +- Verify token signatures +- Check issuer and audience claims + +## Authorization Patterns + +### Role-Based Access Control (RBAC) + +```python +# Example middleware +def require_role(roles: list): + def decorator(handler): + def wrapper(request): + user_roles = get_user_roles(request.user_id) + if not any(role in user_roles for role in roles): + raise ForbiddenError() + return handler(request) + return wrapper + return decorator + +@require_role(['admin', 'moderator']) +def delete_user(request): + pass +``` + +### Permission-Based + +```python +# Check specific permissions +def check_permission(user_id, resource, action): + permissions = get_user_permissions(user_id) + return f"{resource}:{action}" in permissions +``` + +## Security Requirements + +### Password Handling + +- Use bcrypt, scrypt, or Argon2 for hashing +- Minimum 12 character passwords +- Check against breached password lists +- Implement account lockout after failed attempts + +### Token Security + +- Rotate secrets regularly +- Implement token revocation +- Use short-lived access tokens with refresh tokens +- Store refresh tokens securely (httpOnly cookies or encrypted storage) + +### Multi-Factor Authentication + +- Support TOTP (Google Authenticator compatible) +- Consider WebAuthn for passwordless +- Require MFA for sensitive operations + +## Testing Authentication + +### Test Cases Required + +```python +class TestAuthentication: + def test_login_success_returns_token(self): + pass + def test_login_failure_returns_401(self): + pass + def test_invalid_token_returns_401(self): + pass + def test_expired_token_returns_401(self): + pass + def test_missing_token_returns_401(self): + pass + def test_insufficient_permissions_returns_403(self): + pass + def test_token_refresh_works(self): + pass + def test_logout_invalidates_token(self): + pass +``` + +## Authentik SSO Administration + +Authentik is the identity provider for the Mosaic Stack. Use the Authentik tool suite for administration. + +### Tool Suite + +```bash +# System health +~/.config/mosaic/tools/authentik/admin-status.sh + +# User management +~/.config/mosaic/tools/authentik/user-list.sh +~/.config/mosaic/tools/authentik/user-create.sh -u -n -e + +# Group and app management +~/.config/mosaic/tools/authentik/group-list.sh +~/.config/mosaic/tools/authentik/app-list.sh +~/.config/mosaic/tools/authentik/flow-list.sh +``` + +### Registering an OAuth Application + +1. Create an OAuth2 provider in Authentik admin (Applications > Providers) +2. Create an application linked to the provider (Applications > Applications) +3. Configure redirect URIs for the application +4. Store client_id and client_secret in Vault: `secret-{env}/{service}/oauth/authentik/` +5. Verify with: `~/.config/mosaic/tools/authentik/app-list.sh` + +### API Reference + +- Base URL: `https://auth.diversecanvas.com` +- API prefix: `/api/v3/` +- OpenAPI schema: `/api/v3/schema/` +- Auth: Bearer token (obtained via `auth-token.sh`) + +## Common Vulnerabilities to Avoid + +1. **Broken Authentication** + - Weak password requirements + - Missing brute-force protection + - Session fixation + +2. **Broken Access Control** + - Missing authorization checks + - IDOR (Insecure Direct Object Reference) + - Privilege escalation + +3. **Security Misconfiguration** + - Default credentials + - Verbose error messages + - Missing security headers + +## Commit Format + +``` +feat(#89): Implement JWT authentication + +- Add /auth/login and /auth/refresh endpoints +- Implement token validation middleware +- Configure 15min access token expiry + +Fixes #89 +``` diff --git a/packages/mosaic/framework/guides/BACKEND.md b/packages/mosaic/framework/guides/BACKEND.md new file mode 100644 index 0000000..9891061 --- /dev/null +++ b/packages/mosaic/framework/guides/BACKEND.md @@ -0,0 +1,125 @@ +# Backend Development Guide + +## Before Starting + +1. Check assigned issue: `~/.config/mosaic/tools/git/issue-list.sh -a @me` +2. Create scratchpad: `docs/scratchpads/{issue-number}-{short-name}.md` +3. Review API contracts and database schema + +## Development Standards + +### API Design + +- Follow RESTful conventions (or GraphQL patterns if applicable) +- Use consistent endpoint naming: `/api/v1/resource-name` +- Return appropriate HTTP status codes +- Include pagination for list endpoints +- Document all endpoints (OpenAPI/Swagger preferred) + +### Database + +- Write migrations for schema changes +- Use parameterized queries (prevent SQL injection) +- Index frequently queried columns +- Document relationships and constraints + +### Error Handling + +- Return structured error responses +- Log errors with context (request ID, user ID if applicable) +- Never expose internal errors to clients +- Use appropriate error codes + +```json +{ + "error": { + "code": "VALIDATION_ERROR", + "message": "User-friendly message", + "details": [] + } +} +``` + +### Security + +- Validate all input at API boundaries +- Implement rate limiting on public endpoints +- Use secrets from Vault (see `docs/vault-secrets-structure.md`) +- Never log sensitive data (passwords, tokens, PII) +- Follow OWASP guidelines + +### Authentication/Authorization + +- Use project's established auth pattern +- Validate tokens on every request +- Check permissions before operations +- See `~/.config/mosaic/guides/AUTHENTICATION.md` for details + +## Testing Requirements (TDD) + +1. Write tests BEFORE implementation +2. Minimum 85% coverage +3. Test categories: + - Unit tests for business logic + - Integration tests for API endpoints + - Database tests with transactions/rollback + +### Test Patterns + +```python +# API test example structure +class TestResourceEndpoint: + def test_create_returns_201(self): + pass + def test_create_validates_input(self): + pass + def test_get_returns_404_for_missing(self): + pass + def test_requires_authentication(self): + pass +``` + +## Code Style + +- Follow Google Style Guide for your language +- **TypeScript: Follow `~/.config/mosaic/guides/TYPESCRIPT.md` — MANDATORY** +- Use linter/formatter from project configuration +- Keep functions focused and small +- Document complex business logic + +### TypeScript Quick Rules (see TYPESCRIPT.md for full guide) + +- **NO `any`** — define explicit types always +- **NO lazy `unknown`** — only for error catches and external data with validation +- **Explicit return types** on all exported functions +- **Explicit parameter types** always +- **DTO files are REQUIRED** for module/API boundaries (`*.dto.ts`) +- **Interface for DTOs** — never inline object types +- **Typed errors** — use custom error classes + +## Performance + +- Use database connection pooling +- Implement caching where appropriate +- Profile slow endpoints +- Use async operations for I/O + +## Commit Format + +``` +feat(#45): Add user registration endpoint + +- POST /api/v1/users for registration +- Email validation and uniqueness check +- Password hashing with bcrypt + +Fixes #45 +``` + +## Before Completing + +1. Run full test suite +2. Verify migrations work (up and down) +3. Test API with curl/httpie +4. Update scratchpad with completion notes +5. Reference issue in commit diff --git a/packages/mosaic/framework/guides/BOOTSTRAP.md b/packages/mosaic/framework/guides/BOOTSTRAP.md new file mode 100755 index 0000000..1c74b6d --- /dev/null +++ b/packages/mosaic/framework/guides/BOOTSTRAP.md @@ -0,0 +1,487 @@ +# Project Bootstrap Guide + +> Load this guide when setting up a new project for AI-assisted development. + +## Overview + +This guide covers how to bootstrap a project so AI agents (Claude, Codex, etc.) can work on it effectively. Proper bootstrapping ensures: + +1. Agents understand the project structure and conventions +2. Orchestration works correctly with quality gates +3. Independent code review and security review are configured +4. Issue tracking is consistent across projects +5. Documentation standards and API contracts are enforced from day one +6. PRD requirements are established before coding begins +7. Branching/merging is consistent: `branch -> main` via PR with squash-only merges +8. Steered-autonomy execution is enabled so agents can run end-to-end with escalation-only human intervention + +## Quick Start + +```bash +# Automated bootstrap (recommended) +~/.config/mosaic/tools/bootstrap/init-project.sh \ + --name "my-project" \ + --type "nestjs-nextjs" \ + --repo "https://git.mosaicstack.dev/owner/repo" + +# Or manually using templates +export PROJECT_NAME="My Project" +export PROJECT_DESCRIPTION="What this project does" +export TASK_PREFIX="MP" +envsubst < ~/.config/mosaic/templates/agent/AGENTS.md.template > AGENTS.md +envsubst < ~/.config/mosaic/templates/agent/CLAUDE.md.template > CLAUDE.md +``` + +--- + +## Step 0: Enforce Sequential-Thinking MCP (Hard Requirement) + +`sequential-thinking` MCP must be installed and configured before project bootstrapping. + +```bash +# Auto-configure sequential-thinking MCP for installed runtimes +~/.config/mosaic/bin/mosaic-ensure-sequential-thinking + +# Verification-only check +~/.config/mosaic/bin/mosaic-ensure-sequential-thinking --check +``` + +If this step fails, STOP and remediate Mosaic runtime configuration before continuing. + +--- + +## Step 1: Detect Project Type + +Check what files exist in the project root to determine the type: + +| File Present | Project Type | Template | +| ------------------------------------------------------- | ------------------------- | ------------------------- | +| `package.json` + `pnpm-workspace.yaml` + NestJS+Next.js | NestJS + Next.js Monorepo | `projects/nestjs-nextjs/` | +| `pyproject.toml` + `manage.py` | Django | `projects/django/` | +| `pyproject.toml` (no Django) | Python (generic) | Generic template | +| `package.json` (no monorepo) | Node.js (generic) | Generic template | +| Other | Generic | Generic template | + +```bash +# Auto-detect project type +detect_project_type() { + if [[ -f "pnpm-workspace.yaml" ]] && [[ -f "turbo.json" ]]; then + # Check for NestJS + Next.js + if grep -q "nestjs" package.json 2>/dev/null && grep -q "next" package.json 2>/dev/null; then + echo "nestjs-nextjs" + return + fi + fi + if [[ -f "manage.py" ]] && [[ -f "pyproject.toml" ]]; then + echo "django" + return + fi + if [[ -f "pyproject.toml" ]]; then + echo "python" + return + fi + if [[ -f "package.json" ]]; then + echo "nodejs" + return + fi + echo "generic" +} +``` + +--- + +## Step 2: Create AGENTS.md (Primary Project Contract) + +`AGENTS.md` is the primary project-level contract for all agent runtimes. +It defines project-specific requirements, quality gates, patterns, and testing expectations. + +### Using a Tech-Stack Template + +```bash +# Set variables +export PROJECT_NAME="My Project" +export PROJECT_DESCRIPTION="Multi-tenant SaaS platform" +export PROJECT_DIR="my-project" +export REPO_URL="https://git.mosaicstack.dev/owner/repo" +export TASK_PREFIX="MP" + +# Use tech-stack-specific template if available +TYPE=$(detect_project_type) +TEMPLATE_DIR="$HOME/.config/mosaic/templates/agent/projects/$TYPE" + +if [[ -d "$TEMPLATE_DIR" ]]; then + envsubst < "$TEMPLATE_DIR/AGENTS.md.template" > AGENTS.md +else + envsubst < "$HOME/.config/mosaic/templates/agent/AGENTS.md.template" > AGENTS.md +fi +``` + +### Using the Generic Template + +```bash +# Set all required variables +export PROJECT_NAME="My Project" +export PROJECT_DESCRIPTION="What this project does" +export REPO_URL="https://git.mosaicstack.dev/owner/repo" +export PROJECT_DIR="my-project" +export SOURCE_DIR="src" +export CONFIG_FILES="pyproject.toml / package.json" +export FRONTEND_STACK="N/A" +export BACKEND_STACK="Python / FastAPI" +export DATABASE_STACK="PostgreSQL" +export TESTING_STACK="pytest" +export DEPLOYMENT_STACK="Docker" +export BUILD_COMMAND="pip install -e ." +export TEST_COMMAND="pytest tests/" +export LINT_COMMAND="ruff check ." +export TYPECHECK_COMMAND="mypy ." +export QUALITY_GATES="ruff check . && mypy . && pytest tests/" + +envsubst < ~/.config/mosaic/templates/agent/AGENTS.md.template > AGENTS.md +``` + +### Required Sections + +Every AGENTS.md should contain: + +1. **Project description** — One-line summary +2. **Quality gates** — Commands that must pass +3. **Codebase patterns** — Reusable implementation rules +4. **Common gotchas** — Non-obvious constraints +5. **Testing approaches** — Project-specific test strategy +6. **Testing policy** — Situational-first validation and risk-based TDD +7. **Orchestrator integration** — Task prefix, worker checklist +8. **Documentation contract** — Required documentation gates and update expectations +9. **PRD requirement** — `docs/PRD.md` or `docs/PRD.json` required before coding + +--- + +## Step 3: Create Runtime Context File (Runtime-Specific) + +Runtime context files are runtime adapters. They are not the primary project contract. +Use `CLAUDE.md` for Claude runtime compatibility. Use other runtime adapters as required by your environment. + +Claude runtime mandate (HARD RULE): + +- `CLAUDE.md` MUST explicitly instruct Claude agents to read and use `AGENTS.md`. +- `CLAUDE.md` MUST treat `AGENTS.md` as the authoritative project-level contract. +- If `AGENTS.md` and runtime wording conflict, `AGENTS.md` project rules win. + +```bash +TYPE=$(detect_project_type) +TEMPLATE_DIR="$HOME/.config/mosaic/templates/agent/projects/$TYPE" + +if [[ -d "$TEMPLATE_DIR" ]]; then + envsubst < "$TEMPLATE_DIR/CLAUDE.md.template" > CLAUDE.md +else + envsubst < "$HOME/.config/mosaic/templates/agent/CLAUDE.md.template" > CLAUDE.md +fi +``` + +### Required Runtime Sections + +Every runtime context file should contain: + +1. **AGENTS handoff rule** — Runtime MUST direct agents to read/use `AGENTS.md` +2. **Conditional documentation loading** — Required guide loading map +3. **Technology stack** — Runtime-facing architecture summary +4. **Repository structure** — Important paths +5. **Development workflow** — Build/test/lint/typecheck commands +6. **Issue tracking** — Issue and commit conventions +7. **Code review** — Required review process +8. **Runtime notes** — Runtime-specific behavior references +9. **Branch and merge policy** — Trunk workflow (`branch -> main` via PR, squash-only) +10. **Autonomy and escalation policy** — Agent owns coding/review/PR/release/deploy lifecycle + +--- + +## Step 4: Create Directory Structure + +```bash +# Create standard directories +mkdir -p docs/scratchpads +mkdir -p docs/templates +mkdir -p docs/reports/qa-automation/pending +mkdir -p docs/reports/qa-automation/in-progress +mkdir -p docs/reports/qa-automation/done +mkdir -p docs/reports/qa-automation/escalated +mkdir -p docs/reports/deferred +mkdir -p docs/tasks +mkdir -p docs/releases +mkdir -p docs/USER-GUIDE docs/ADMIN-GUIDE docs/DEVELOPER-GUIDE docs/API + +# Documentation baseline files +touch docs/USER-GUIDE/README.md +touch docs/ADMIN-GUIDE/README.md +touch docs/DEVELOPER-GUIDE/README.md +touch docs/API/OPENAPI.yaml +touch docs/API/ENDPOINTS.md +touch docs/SITEMAP.md + +# PRD baseline file (requirements source before coding) +cp ~/.config/mosaic/templates/docs/PRD.md.template docs/PRD.md + +# TASKS baseline file (canonical tracking) +cp ~/.config/mosaic/templates/docs/TASKS.md.template docs/TASKS.md + +# Deployment baseline file (target/platform/runbook) +touch docs/DEPLOYMENT.md +``` + +Documentation root hygiene (HARD RULE): + +- Keep `docs/` root clean. +- Store reports in `docs/reports/`, archived task artifacts in `docs/tasks/`, releases in `docs/releases/`, and scratchpads in `docs/scratchpads/`. +- Do not place ad-hoc report files directly under `docs/`. + +--- + +## Step 5: Initialize Repository Labels & Milestones + +```bash +# Use the init script +~/.config/mosaic/tools/bootstrap/init-repo-labels.sh + +# Or manually create standard labels +~/.config/mosaic/tools/git/issue-create.sh # (labels are created on first use) +``` + +### Standard Labels + +| Label | Color | Purpose | +| --------------- | --------- | -------------------------------------- | +| `epic` | `#3E4B9E` | Large feature spanning multiple issues | +| `feature` | `#0E8A16` | New functionality | +| `bug` | `#D73A4A` | Defect fix | +| `task` | `#0075CA` | General work item | +| `documentation` | `#0075CA` | Documentation updates | +| `security` | `#B60205` | Security-related | +| `breaking` | `#D93F0B` | Breaking change | + +### Initial Milestone (Hard Rule) + +Create the first pre-MVP milestone at `0.0.1`. +Reserve `0.1.0` for the MVP release milestone. + +```bash +~/.config/mosaic/tools/git/milestone-create.sh -t "0.0.1" -d "Pre-MVP - Foundation Sprint" + +# Create when MVP scope is complete and release-ready: +~/.config/mosaic/tools/git/milestone-create.sh -t "0.1.0" -d "MVP - Minimum Viable Product" +``` + +--- + +## Step 5b: Configure Main Branch Protection (Hard Rule) + +Apply equivalent settings in Gitea, GitHub, or GitLab: + +1. Protect `main` from direct pushes. +2. Require pull requests to merge into `main`. +3. Require required CI/status checks to pass before merge. +4. Require code review approval before merge. +5. Allow **squash merge only** for PRs into `main` (disable merge commits and rebase merges for `main`). + +This enforces one merge strategy across human and agent workflows. + +--- + +## Step 6: Set Up CI/CD Review Pipeline + +### Woodpecker CI + +```bash +# Copy Codex review pipeline +mkdir -p .woodpecker/schemas +cp ~/.config/mosaic/tools/codex/woodpecker/codex-review.yml .woodpecker/ +cp ~/.config/mosaic/tools/codex/schemas/*.json .woodpecker/schemas/ + +# Add codex_api_key secret to Woodpecker CI dashboard +``` + +### GitHub Actions + +For GitHub repos, use the official Codex GitHub Action instead: + +```yaml +# .github/workflows/codex-review.yml +uses: openai/codex-action@v1 +``` + +### Python Package Publishing (Gitea PyPI) + +If the project publishes Python packages, use Gitea's PyPI registry. + +```bash +# Build and publish +python -m pip install --upgrade build twine +python -m build +python -m twine upload \ + --repository-url "https://GITEA_HOST/api/packages/ORG/pypi" \ + --username "$GITEA_USERNAME" \ + --password "$GITEA_TOKEN" \ + dist/* +``` + +Use the same `gitea_username` and `gitea_token` CI secrets used for container and npm publishing. + +--- + +## Step 7: Verify Bootstrap + +After bootstrapping, verify everything works: + +```bash +# Check files exist +ls AGENTS.md docs/scratchpads/ +ls docs/reports/qa-automation/pending docs/reports/deferred docs/tasks docs/releases +ls docs/USER-GUIDE/README.md docs/ADMIN-GUIDE/README.md docs/DEVELOPER-GUIDE/README.md +ls docs/API/OPENAPI.yaml docs/API/ENDPOINTS.md docs/SITEMAP.md +ls docs/PRD.md +ls docs/TASKS.md + +# Verify AGENTS.md has required sections +grep -c "Quality Gates" AGENTS.md +grep -c "Orchestrator Integration" AGENTS.md +grep -c "Testing Approaches" AGENTS.md +grep -c "Testing Policy" AGENTS.md +grep -c "Documentation Contract" AGENTS.md +grep -c "PRD Requirement" AGENTS.md + +# Verify runtime context file has required sections +if [[ -f CLAUDE.md ]]; then + grep -c "AGENTS.md" CLAUDE.md + grep -c "Conditional Documentation Loading" CLAUDE.md + grep -c "Technology Stack" CLAUDE.md + grep -c "Code Review" CLAUDE.md +elif [[ -f RUNTIME.md ]]; then + grep -c "Conditional Documentation Loading" RUNTIME.md + grep -c "Technology Stack" RUNTIME.md + grep -c "Code Review" RUNTIME.md +else + echo "Missing runtime context file (CLAUDE.md or RUNTIME.md)" >&2 + exit 1 +fi + +# Run quality gates from AGENTS.md +# (execute the command block under "Quality Gates") + +# Test Codex review (if configured) +~/.config/mosaic/tools/codex/codex-code-review.sh --help + +# Verify sequential-thinking MCP remains configured +~/.config/mosaic/bin/mosaic-ensure-sequential-thinking --check +``` + +--- + +## Available Templates + +### Generic Templates + +| Template | Path | Purpose | +| ---------------------------- | ----------------------------------- | ------------------------------------------ | +| `AGENTS.md.template` | `~/.config/mosaic/templates/agent/` | Primary project agent contract | +| `CLAUDE.md.template` | `~/.config/mosaic/templates/agent/` | Runtime compatibility context (Claude) | +| `DOCUMENTATION-CHECKLIST.md` | `~/.config/mosaic/templates/docs/` | Documentation completion gate | +| `PRD.md.template` | `~/.config/mosaic/templates/docs/` | Requirements source template | +| `TASKS.md.template` | `~/.config/mosaic/templates/docs/` | Canonical task and issue tracking template | + +### Tech-Stack Templates + +| Stack | Path | Includes | +| ---------------- | ---------------------------------------------------------- | ------------------------------------ | +| NestJS + Next.js | `~/.config/mosaic/templates/agent/projects/nestjs-nextjs/` | AGENTS.md + runtime context template | +| Django | `~/.config/mosaic/templates/agent/projects/django/` | AGENTS.md + runtime context template | + +### Orchestrator Templates + +| Template | Path | Purpose | +| -------------------------------------- | ------------------------------------------------- | ----------------------- | +| `tasks.md.template` | `~/src/jarvis-brain/docs/templates/orchestrator/` | Task tracking | +| `orchestrator-learnings.json.template` | `~/src/jarvis-brain/docs/templates/orchestrator/` | Variance tracking | +| `phase-issue-body.md.template` | `~/src/jarvis-brain/docs/templates/orchestrator/` | Git provider issue body | +| `scratchpad.md.template` | `~/src/jarvis-brain/docs/templates/` | Per-task working doc | + +### Variables Reference + +| Variable | Description | Example | +| ------------------------ | --------------------------- | ------------------------------------------ | +| `${PROJECT_NAME}` | Human-readable project name | "Mosaic Stack" | +| `${PROJECT_DESCRIPTION}` | One-line description | "Multi-tenant platform" | +| `${PROJECT_DIR}` | Directory name | "mosaic-stack" | +| `${PROJECT_SLUG}` | Python package slug | "mosaic_stack" | +| `${REPO_URL}` | Git remote URL | "https://git.mosaicstack.dev/mosaic/stack" | +| `${TASK_PREFIX}` | Orchestrator task prefix | "MS" | +| `${SOURCE_DIR}` | Source code directory | "src" or "apps" | +| `${QUALITY_GATES}` | Quality gate commands | "pnpm typecheck && pnpm lint && pnpm test" | +| `${BUILD_COMMAND}` | Build command | "pnpm build" | +| `${TEST_COMMAND}` | Test command | "pnpm test" | +| `${LINT_COMMAND}` | Lint command | "pnpm lint" | +| `${TYPECHECK_COMMAND}` | Type check command | "pnpm typecheck" | +| `${FRONTEND_STACK}` | Frontend technologies | "Next.js + React" | +| `${BACKEND_STACK}` | Backend technologies | "NestJS + Prisma" | +| `${DATABASE_STACK}` | Database technologies | "PostgreSQL" | +| `${TESTING_STACK}` | Testing technologies | "Vitest + Playwright" | +| `${DEPLOYMENT_STACK}` | Deployment technologies | "Docker" | +| `${CONFIG_FILES}` | Key config files | "package.json, tsconfig.json" | + +--- + +## Bootstrap Scripts + +### init-project.sh + +Full project bootstrap with interactive and flag-based modes: + +```bash +~/.config/mosaic/tools/bootstrap/init-project.sh \ + --name "My Project" \ + --type "nestjs-nextjs" \ + --repo "https://git.mosaicstack.dev/owner/repo" \ + --prefix "MP" \ + --description "Multi-tenant platform" +``` + +### init-repo-labels.sh + +Initialize standard labels and the first pre-MVP milestone: + +```bash +~/.config/mosaic/tools/bootstrap/init-repo-labels.sh +``` + +--- + +## Checklist + +After bootstrapping, verify: + +- [ ] `AGENTS.md` exists and is the primary project contract +- [ ] Runtime context file exists (`CLAUDE.md` or `RUNTIME.md`) +- [ ] `docs/scratchpads/` directory exists +- [ ] `docs/reports/qa-automation/pending` directory exists +- [ ] `docs/reports/deferred/` directory exists +- [ ] `docs/tasks/` directory exists +- [ ] `docs/releases/` directory exists +- [ ] `docs/USER-GUIDE/README.md` exists +- [ ] `docs/ADMIN-GUIDE/README.md` exists +- [ ] `docs/DEVELOPER-GUIDE/README.md` exists +- [ ] `docs/API/OPENAPI.yaml` exists +- [ ] `docs/API/ENDPOINTS.md` exists +- [ ] `docs/SITEMAP.md` exists +- [ ] `docs/PRD.md` or `docs/PRD.json` exists +- [ ] `docs/TASKS.md` exists and is ready for active tracking +- [ ] `docs/DEPLOYMENT.md` exists with target platform and rollback notes +- [ ] `sequential-thinking` MCP is configured and verification check passes +- [ ] Git labels created (epic, feature, bug, task, etc.) +- [ ] Initial pre-MVP milestone created (0.0.1) +- [ ] MVP milestone reserved for release (0.1.0) +- [ ] `main` is protected from direct pushes +- [ ] PRs into `main` are required +- [ ] Merge method for `main` is squash-only +- [ ] Quality gates run successfully +- [ ] `.env.example` exists (if project uses env vars) +- [ ] CI/CD pipeline configured (if using Woodpecker/GitHub Actions) +- [ ] Python publish path configured in CI (if project ships Python packages) +- [ ] Codex review scripts accessible (`~/.config/mosaic/tools/codex/`) diff --git a/packages/mosaic/framework/guides/CI-CD-PIPELINES.md b/packages/mosaic/framework/guides/CI-CD-PIPELINES.md new file mode 100644 index 0000000..3766b14 --- /dev/null +++ b/packages/mosaic/framework/guides/CI-CD-PIPELINES.md @@ -0,0 +1,1082 @@ +# CI/CD Pipeline Guide + +> **Load this guide when:** Adding Docker build/push steps, configuring Woodpecker CI pipelines, publishing packages to registries, or implementing CI/CD for a new project. + +## Overview + +This guide covers the canonical CI/CD pattern used across projects. The pipeline runs in Woodpecker CI and follows this flow: + +``` +GIT PUSH + ↓ +QUALITY GATES (lint, typecheck, test, audit) + ↓ all pass +BUILD (compile all packages) + ↓ only on main/tags +DOCKER BUILD & PUSH (Kaniko → Gitea Container Registry) + ↓ all images pushed +PACKAGE LINKING (associate images with repository in Gitea) +``` + +## Reference Implementations + +### Split Pipelines (Preferred for Monorepos) + +**Mosaic Telemetry** (`~/src/mosaic-telemetry-monorepo/.woodpecker/`) is the canonical example of **split per-package pipelines** with path filtering, full security chain (source + container scanning), and efficient CI resource usage. + +**Key features:** + +- One YAML per package in `.woodpecker/` directory +- Path filtering: only the affected package's pipeline runs on push +- Security chain: source scanning (bandit/npm audit) + dependency audit (pip-audit) + container scanning (Trivy) +- Docker build gates on ALL quality steps + +**Always use this pattern for monorepos.** It saves CI minutes and isolates failures. + +### Single Pipeline (Legacy/Simple Projects) + +**Mosaic Stack** (`~/src/mosaic-stack/.woodpecker/build.yml`) uses a single pipeline that builds everything on every push. This works but wastes CI resources on large monorepos. **Mosaic Stack is scheduled for migration to split pipelines.** + +Always read the telemetry pipelines first when implementing a new pipeline. + +## Infrastructure Instances + +| Project | Gitea | Woodpecker | Registry | +| ------------ | --------------------- | ----------------------- | --------------------- | +| Mosaic Stack | `git.mosaicstack.dev` | `ci.mosaicstack.dev` | `git.mosaicstack.dev` | +| U-Connect | `git.uscllc.com` | `woodpecker.uscllc.net` | `git.uscllc.com` | + +The patterns are identical — only the hostnames and org/repo names differ. + +## Woodpecker Pipeline Structure + +### YAML Anchors (DRY) + +Define reusable values at the top of `.woodpecker.yml`: + +```yaml +variables: + - &node_image 'node:20-alpine' + - &install_deps | + corepack enable + npm ci + # For pnpm projects, use: + # - &install_deps | + # corepack enable + # pnpm install --frozen-lockfile + - &kaniko_setup | + mkdir -p /kaniko/.docker + echo "{\"auths\":{\"REGISTRY_HOST\":{\"username\":\"$GITEA_USER\",\"password\":\"$GITEA_TOKEN\"}}}" > /kaniko/.docker/config.json +``` + +Replace `REGISTRY_HOST` with the actual Gitea hostname (e.g., `git.uscllc.com`). + +### Step Dependencies + +Woodpecker runs steps in parallel by default. Use `depends_on` to create the dependency graph: + +```yaml +steps: + install: + image: *node_image + commands: + - *install_deps + + lint: + image: *node_image + commands: + - npm run lint + depends_on: + - install + + typecheck: + image: *node_image + commands: + - npm run type-check + depends_on: + - install + + test: + image: *node_image + commands: + - npm run test + depends_on: + - install + + build: + image: *node_image + environment: + NODE_ENV: "production" + commands: + - npm run build + depends_on: + - lint + - typecheck + - test +``` + +### Conditional Execution + +Use `when` clauses to limit expensive steps (Docker builds) to relevant branches: + +```yaml +when: + # Top-level: run quality gates on everything + - event: [push, pull_request, manual] + +# Per-step: only build Docker images on main/tags +docker-build-api: + when: + - branch: [main] + event: [push, manual, tag] +``` + +## Docker Build & Push with Kaniko + +### Why Kaniko + +Kaniko builds container images without requiring a Docker daemon. This is the standard approach in Woodpecker CI because: + +- No privileged mode needed +- No Docker-in-Docker security concerns +- Multi-destination tagging in a single build +- Works in any container runtime + +### Kaniko Step Template + +```yaml +docker-build-SERVICE: + image: gcr.io/kaniko-project/executor:debug + environment: + GITEA_USER: + from_secret: gitea_username + GITEA_TOKEN: + from_secret: gitea_token + RELEASE_BASE_VERSION: ${RELEASE_BASE_VERSION} + CI_COMMIT_BRANCH: ${CI_COMMIT_BRANCH} + CI_COMMIT_TAG: ${CI_COMMIT_TAG} + CI_COMMIT_SHA: ${CI_COMMIT_SHA} + CI_PIPELINE_NUMBER: ${CI_PIPELINE_NUMBER} + commands: + - *kaniko_setup + - | + SHORT_SHA="${CI_COMMIT_SHA:0:8}" + BUILD_ID="${CI_PIPELINE_NUMBER:-$SHORT_SHA}" + BASE_VERSION="${RELEASE_BASE_VERSION:?RELEASE_BASE_VERSION is required (example: 0.0.1)}" + + DESTINATIONS="--destination REGISTRY/ORG/IMAGE_NAME:sha-$SHORT_SHA" + if [ "$CI_COMMIT_BRANCH" = "main" ]; then + DESTINATIONS="$DESTINATIONS --destination REGISTRY/ORG/IMAGE_NAME:v${BASE_VERSION}-rc.${BUILD_ID}" + DESTINATIONS="$DESTINATIONS --destination REGISTRY/ORG/IMAGE_NAME:testing" + fi + if [ -n "$CI_COMMIT_TAG" ]; then + DESTINATIONS="$DESTINATIONS --destination REGISTRY/ORG/IMAGE_NAME:$CI_COMMIT_TAG" + fi + /kaniko/executor --context . --dockerfile PATH/TO/Dockerfile $DESTINATIONS + when: + - branch: [main] + event: [push, manual, tag] + depends_on: + - build +``` + +**Replace these placeholders:** + +| Placeholder | Example (Mosaic) | Example (U-Connect) | +| -------------------- | --------------------- | ---------------------------- | +| `REGISTRY` | `git.mosaicstack.dev` | `git.uscllc.com` | +| `ORG` | `mosaic` | `usc` | +| `IMAGE_NAME` | `stack-api` | `uconnect-backend-api` | +| `PATH/TO/Dockerfile` | `apps/api/Dockerfile` | `src/backend-api/Dockerfile` | + +### Image Tagging Strategy + +Tagging MUST follow a two-layer model: immutable identity tags + mutable environment tags. + +Immutable tags: + +| Condition | Tag | Purpose | +| ------------------------ | ------------------------------- | ------------------------------------------------------- | +| Always | `sha-${CI_COMMIT_SHA:0:8}` | Immutable reference to exact commit | +| `main` branch | `v{BASE_VERSION}-rc.{BUILD_ID}` | Intermediate release candidate for the active milestone | +| Git tag (e.g., `v1.0.0`) | `v1.0.0` | Semantic version release | + +Mutable environment tags: + +| Tag | Purpose | +| -------------------- | ---------------------------------------------- | +| `testing` | Current candidate under situational validation | +| `staging` (optional) | Pre-production validation target | +| `prod` | Current production pointer | + +Hard rules: + +- Do NOT use `latest` for deployment. +- Do NOT use `dev` as the primary deployment tag. +- Deployments MUST resolve to an immutable image digest. + +### Digest-First Promotion (Hard Rule) + +Deploy and promote by digest, not by mutable tag: + +1. Build and push candidate tags (`sha-*`, `vX.Y.Z-rc.N`, `testing`). +2. Resolve the digest from `sha-*` tag. +3. Deploy that digest to testing and run situational tests. +4. If green, promote the same digest to `staging`/`prod` tags. +5. Create final semantic release tag (`vX.Y.Z`) only at milestone completion. + +Example with `crane`: + +```bash +DIGEST=$(crane digest REGISTRY/ORG/IMAGE:sha-${CI_COMMIT_SHA:0:8}) +crane tag REGISTRY/ORG/IMAGE@${DIGEST} testing +# after situational tests pass: +crane tag REGISTRY/ORG/IMAGE@${DIGEST} prod +``` + +### Deployment Strategy: Blue-Green Default + +- Blue-green is the default release strategy for lights-out operation. +- Canary is OPTIONAL and allowed only when automated SLO/error-rate monitoring and rollback triggers are configured. +- If canary guardrails are missing, you MUST use blue-green. + +### Image Retention and Cleanup (Hard Rule) + +Registry cleanup MUST be automated (daily or weekly job). + +Retention policy: + +- Keep all final release tags (`vX.Y.Z`) indefinitely. +- Keep digests currently referenced by `prod` and `testing` tags. +- Keep the most recent 20 RC tags (`vX.Y.Z-rc.N`) per service. +- Delete RC and `sha-*` tags older than 30 days when they are not referenced by active environments/releases. + +Before deleting any image/tag: + +- Verify digest is not currently deployed. +- Verify digest is not referenced by any active release/tag notes. +- Log cleanup actions in CI job output. + +### Kaniko Options + +Common flags for `/kaniko/executor`: + +| Flag | Purpose | +| --------------------------------------- | ------------------------ | +| `--context .` | Build context directory | +| `--dockerfile path/Dockerfile` | Dockerfile location | +| `--destination registry/org/image:tag` | Push target (repeatable) | +| `--build-arg KEY=VALUE` | Pass build arguments | +| `--cache=true` | Enable layer caching | +| `--cache-repo registry/org/image-cache` | Cache storage location | + +### Build Arguments + +Pass environment-specific values at build time: + +```yaml +/kaniko/executor --context . --dockerfile apps/web/Dockerfile \ +--build-arg NEXT_PUBLIC_API_URL=https://api.example.com \ +$DESTINATIONS +``` + +## Gitea Container Registry + +### How It Works + +Gitea has a built-in container registry. When you push an image to `git.example.com/org/image:tag`, Gitea stores it and makes it available in the Packages section. + +### Authentication + +Kaniko authenticates via a Docker config file created at pipeline start: + +```json +{ + "auths": { + "git.example.com": { + "username": "GITEA_USER", + "password": "GITEA_TOKEN" + } + } +} +``` + +The token must have `package:write` scope. Generate it at: `https://GITEA_HOST/user/settings/applications` + +### Pulling Images + +After pushing, images are available at: + +```bash +docker pull git.example.com/org/image:tag +``` + +In `docker-compose.yml`: + +```yaml +services: + api: + # Preferred: pin digest produced by CI and promoted by environment + image: git.example.com/org/image@${IMAGE_DIGEST} + # Optional channel pointer for non-prod: + # image: git.example.com/org/image:${IMAGE_TAG:-testing} +``` + +## Package Linking + +After pushing images to the Gitea registry, link them to the source repository so they appear on the repository's Packages tab. + +### Gitea Package Linking API + +``` +POST /api/v1/packages/{owner}/{type}/{name}/-/link/{repo} +``` + +| Parameter | Value | +| --------- | ------------------------------------------- | +| `owner` | Organization name (e.g., `mosaic`, `usc`) | +| `type` | `container` | +| `name` | Image name (e.g., `stack-api`) | +| `repo` | Repository name (e.g., `stack`, `uconnect`) | + +### Link Step Template + +```yaml +link-packages: + image: alpine:3 + environment: + GITEA_TOKEN: + from_secret: gitea_token + commands: + - apk add --no-cache curl + - echo "Waiting 10 seconds for packages to be indexed..." + - sleep 10 + - | + set -e + link_package() { + PKG="$$1" + echo "Linking $$PKG..." + + for attempt in 1 2 3; do + STATUS=$$(curl -s -o /tmp/link-response.txt -w "%{http_code}" -X POST \ + -H "Authorization: token $$GITEA_TOKEN" \ + "https://GITEA_HOST/api/v1/packages/ORG/container/$$PKG/-/link/REPO") + + if [ "$$STATUS" = "201" ] || [ "$$STATUS" = "204" ]; then + echo " Linked $$PKG" + return 0 + elif [ "$$STATUS" = "400" ]; then + echo " $$PKG already linked" + return 0 + elif [ "$$STATUS" = "404" ] && [ $$attempt -lt 3 ]; then + echo " $$PKG not found yet, retrying in 5s (attempt $$attempt/3)..." + sleep 5 + else + echo " FAILED: $$PKG status $$STATUS" + cat /tmp/link-response.txt + return 1 + fi + done + } + + link_package "image-name-1" + link_package "image-name-2" + when: + - branch: [main] + event: [push, manual, tag] + depends_on: + - docker-build-image-1 + - docker-build-image-2 +``` + +**Replace:** `GITEA_HOST`, `ORG`, `REPO`, and the `link_package` calls with actual image names. + +**Note on `$$`:** Woodpecker uses `$$` to escape `$` in shell commands within YAML. Use `$$` for shell variables and `${CI_*}` (single `$`) for Woodpecker CI variables. + +### Status Codes + +| Code | Meaning | Action | +| ---- | ----------- | -------------------------------------- | +| 201 | Created | Success | +| 204 | No content | Success | +| 400 | Bad request | Already linked (OK) | +| 404 | Not found | Retry — package may not be indexed yet | + +### Known Issue + +The Gitea package linking API (added in Gitea 1.24.0) can return 404 for recently pushed packages. The retry logic with 5-second delays handles this. If linking still fails, packages are usable — they just won't appear on the repository Packages tab. They can be linked manually via the Gitea web UI. + +## Woodpecker Secrets + +### Required Secrets + +Configure these in the Woodpecker UI (Settings > Secrets) or via CLI: + +| Secret Name | Value | Scope | +| ---------------- | -------------------------------------- | ----------------------- | +| `gitea_username` | Gitea username or service account | `push`, `manual`, `tag` | +| `gitea_token` | Gitea token with `package:write` scope | `push`, `manual`, `tag` | + +### Required CI Variables (Non-Secret) + +| Variable | Example | Purpose | +| ---------------------- | ------- | --------------------------------------------------------------- | +| `RELEASE_BASE_VERSION` | `0.0.1` | Base milestone version used to generate RC tags (`v0.0.1-rc.N`) | + +### Setting Secrets via CLI + +```bash +# Woodpecker CLI +woodpecker secret add ORG/REPO --name gitea_username --value "USERNAME" +woodpecker secret add ORG/REPO --name gitea_token --value "TOKEN" +``` + +### Security Rules + +- Never hardcode tokens in pipeline YAML +- Use `from_secret` for all credentials +- Limit secret event scope (don't expose on `pull_request` from forks) +- Use dedicated service accounts, not personal tokens +- Rotate tokens periodically + +## npm Package Publishing + +For projects with publishable npm packages (e.g., shared libraries, design systems). + +### Publishing to Gitea npm Registry + +Gitea includes a built-in npm registry at `https://GITEA_HOST/api/packages/ORG/npm/`. + +**Pipeline step:** + +```yaml +publish-packages: + image: *node_image + environment: + GITEA_TOKEN: + from_secret: gitea_token + commands: + - | + echo "//GITEA_HOST/api/packages/ORG/npm/:_authToken=$$GITEA_TOKEN" > .npmrc + echo "@SCOPE:registry=https://GITEA_HOST/api/packages/ORG/npm/" >> .npmrc + - npm publish -w @SCOPE/package-name + when: + - branch: [main] + event: [push, manual, tag] + depends_on: + - build +``` + +**Replace:** `GITEA_HOST`, `ORG`, `SCOPE`, `package-name`. + +### Why Gitea npm (not Verdaccio) + +Gitea's built-in npm registry eliminates the need for a separate Verdaccio instance. Benefits: + +- **Same auth** — Gitea token with `package:write` scope works for git, containers, AND npm +- **No extra service** — No Verdaccio container, no OAuth/Authentik integration, no separate compose stack +- **Same UI** — Packages appear alongside container images in Gitea's Packages tab +- **Same secrets** — `gitea_token` in Woodpecker handles both Docker push and npm publish + +If a project currently uses Verdaccio (e.g., U-Connect at `npm.uscllc.net`), migrate to Gitea npm. See the migration checklist below. + +### Versioning + +Only publish when the version in `package.json` has changed. Add a version check: + +```yaml +commands: + - | + CURRENT=$(node -p "require('./src/PACKAGE/package.json').version") + PUBLISHED=$(npm view @SCOPE/PACKAGE version 2>/dev/null || echo "0.0.0") + if [ "$CURRENT" = "$PUBLISHED" ]; then + echo "Version $CURRENT already published, skipping" + exit 0 + fi + echo "Publishing $CURRENT (was $PUBLISHED)" + npm publish -w @SCOPE/PACKAGE +``` + +## CI Services (Test Databases) + +For projects that need a database during CI (migrations, integration tests): + +```yaml +services: + postgres: + image: postgres:17-alpine + environment: + POSTGRES_DB: test_db + POSTGRES_USER: test_user + POSTGRES_PASSWORD: test_password + +steps: + test: + image: *node_image + environment: + DATABASE_URL: "postgresql://test_user:test_password@postgres:5432/test_db?schema=public" + commands: + - npm run test + depends_on: + - install +``` + +The service name (`postgres`) becomes the hostname within the pipeline network. + +## Split Pipelines for Monorepos (REQUIRED) + +For any monorepo with multiple packages/apps, use **split pipelines** — one YAML per package in `.woodpecker/`. + +### Why Split? + +| Aspect | Single pipeline | Split pipelines | +| ----------------- | ----------------------------- | -------------------------------- | +| Path filtering | None — everything rebuilds | Per-package — only affected code | +| Security scanning | Often missing | Required per-package | +| CI minutes | Wasted on unaffected packages | Efficient | +| Failure isolation | One failure blocks everything | Per-package failures isolated | +| Readability | One massive file | Focused, maintainable | + +### Structure + +``` +.woodpecker/ +├── api.yml # Only runs when apps/api/** changes +├── web.yml # Only runs when apps/web/** changes +└── (infra.yml) # Optional: shared infra (DB images, etc.) +``` + +**IMPORTANT:** Do NOT also have `.woodpecker.yml` at root — `.woodpecker/` directory takes precedence and the `.yml` file will be silently ignored. + +### Path Filtering Template + +```yaml +when: + - event: [push, pull_request, manual] + path: + include: ['apps/api/**', '.woodpecker/api.yml'] +``` + +Each pipeline self-triggers on its own YAML changes. Manual triggers run regardless of path. + +### Kaniko Context Scoping + +In split pipelines, scope the Kaniko context to the app directory: + +```yaml +/kaniko/executor --context apps/api --dockerfile apps/api/Dockerfile $$DESTINATIONS +``` + +This means Dockerfile `COPY . .` only copies the app's files, not the entire monorepo. + +### Reference: Telemetry Split Pipeline + +See `~/src/mosaic-telemetry-monorepo/.woodpecker/api.yml` and `web.yml` for a complete working example with path filtering, security chain, and Trivy scanning. + +## Security Scanning (REQUIRED) + +Every pipeline MUST include security scanning. Docker build steps MUST gate on all security steps passing. + +### Source-Level Security (per tech stack) + +**Python:** + +```yaml +security-bandit: + image: *uv_image + commands: + - | + cd apps/api + uv sync --all-extras --frozen + uv run bandit -r src/ -f screen + depends_on: [install] + +security-audit: + image: *uv_image + commands: + - | + cd apps/api + uv sync --all-extras --frozen + uv run pip-audit + depends_on: [install] +``` + +**Node.js:** + +```yaml +security-audit: + image: node:22-alpine + commands: + - cd apps/web && npm audit --audit-level=high + depends_on: [install] +``` + +### Container Scanning (Trivy) — Post-Build + +Run Trivy against every built image to catch OS-level and runtime vulnerabilities: + +```yaml +security-trivy: + image: aquasec/trivy:latest + environment: + GITEA_USER: + from_secret: gitea_username + GITEA_TOKEN: + from_secret: gitea_token + CI_COMMIT_SHA: ${CI_COMMIT_SHA} + commands: + - | + mkdir -p ~/.docker + echo "{\"auths\":{\"REGISTRY\":{\"username\":\"$$GITEA_USER\",\"password\":\"$$GITEA_TOKEN\"}}}" > ~/.docker/config.json + trivy image --exit-code 1 --severity HIGH,CRITICAL --ignore-unfixed \ + REGISTRY/ORG/IMAGE:sha-$${CI_COMMIT_SHA:0:8} + when: + - branch: [main] + event: [push, manual, tag] + depends_on: + - docker-build-SERVICE +``` + +**Replace:** `REGISTRY`, `ORG`, `IMAGE`, `SERVICE`. + +### Full Dependency Chain + +``` +install → [lint, typecheck, security-source, security-deps, test] → docker-build → trivy → link-package +``` + +Docker build MUST depend on ALL quality + security steps. Trivy runs AFTER build. Package linking runs AFTER Trivy. + +## Monorepo Considerations + +### pnpm + Turbo + +```yaml +variables: + - &install_deps | + corepack enable + pnpm install --frozen-lockfile + +steps: + build: + commands: + - *install_deps + - pnpm build # Turbo handles dependency order and caching +``` + +### npm Workspaces + +```yaml +variables: + - &install_deps | + corepack enable + npm ci + +steps: + # Build shared dependencies first + build-deps: + commands: + - npm run build -w @scope/shared-auth + - npm run build -w @scope/shared-types + + # Then build everything + build-all: + commands: + - npm run build -w @scope/package-1 + - npm run build -w @scope/package-2 + # ... in dependency order + depends_on: + - build-deps +``` + +### Per-Package Quality Checks + +For large monorepos, run checks per-package in parallel: + +```yaml +lint-api: + commands: + - npm run lint -w @scope/api + depends_on: [install] + +lint-web: + commands: + - npm run lint -w @scope/web + depends_on: [install] + +# These run in parallel since they share the same dependency +``` + +## Complete Pipeline Example + +This is a minimal but complete pipeline for a project with two services: + +```yaml +when: + - event: [push, pull_request, manual] + +variables: + - &node_image "node:20-alpine" + - &install_deps | + corepack enable + npm ci + - &kaniko_setup | + mkdir -p /kaniko/.docker + echo "{\"auths\":{\"git.example.com\":{\"username\":\"$GITEA_USER\",\"password\":\"$GITEA_TOKEN\"}}}" > /kaniko/.docker/config.json + +steps: + # === Quality Gates === + install: + image: *node_image + commands: + - *install_deps + + lint: + image: *node_image + commands: + - npm run lint + depends_on: [install] + + test: + image: *node_image + commands: + - npm run test + depends_on: [install] + + build: + image: *node_image + environment: + NODE_ENV: "production" + commands: + - npm run build + depends_on: [lint, test] + + # === Docker Build & Push === + docker-build-api: + image: gcr.io/kaniko-project/executor:debug + environment: + GITEA_USER: + from_secret: gitea_username + GITEA_TOKEN: + from_secret: gitea_token + RELEASE_BASE_VERSION: ${RELEASE_BASE_VERSION} + CI_COMMIT_BRANCH: ${CI_COMMIT_BRANCH} + CI_COMMIT_TAG: ${CI_COMMIT_TAG} + CI_COMMIT_SHA: ${CI_COMMIT_SHA} + CI_PIPELINE_NUMBER: ${CI_PIPELINE_NUMBER} + commands: + - *kaniko_setup + - | + SHORT_SHA="${CI_COMMIT_SHA:0:8}" + BUILD_ID="${CI_PIPELINE_NUMBER:-$SHORT_SHA}" + BASE_VERSION="${RELEASE_BASE_VERSION:?RELEASE_BASE_VERSION is required}" + DESTINATIONS="--destination git.example.com/org/api:sha-$SHORT_SHA" + if [ "$CI_COMMIT_BRANCH" = "main" ]; then + DESTINATIONS="$DESTINATIONS --destination git.example.com/org/api:v${BASE_VERSION}-rc.${BUILD_ID}" + DESTINATIONS="$DESTINATIONS --destination git.example.com/org/api:testing" + fi + if [ -n "$CI_COMMIT_TAG" ]; then + DESTINATIONS="$DESTINATIONS --destination git.example.com/org/api:$CI_COMMIT_TAG" + fi + /kaniko/executor --context . --dockerfile src/api/Dockerfile $DESTINATIONS + when: + - branch: [main] + event: [push, manual, tag] + depends_on: [build] + + docker-build-web: + image: gcr.io/kaniko-project/executor:debug + environment: + GITEA_USER: + from_secret: gitea_username + GITEA_TOKEN: + from_secret: gitea_token + RELEASE_BASE_VERSION: ${RELEASE_BASE_VERSION} + CI_COMMIT_BRANCH: ${CI_COMMIT_BRANCH} + CI_COMMIT_TAG: ${CI_COMMIT_TAG} + CI_COMMIT_SHA: ${CI_COMMIT_SHA} + CI_PIPELINE_NUMBER: ${CI_PIPELINE_NUMBER} + commands: + - *kaniko_setup + - | + SHORT_SHA="${CI_COMMIT_SHA:0:8}" + BUILD_ID="${CI_PIPELINE_NUMBER:-$SHORT_SHA}" + BASE_VERSION="${RELEASE_BASE_VERSION:?RELEASE_BASE_VERSION is required}" + DESTINATIONS="--destination git.example.com/org/web:sha-$SHORT_SHA" + if [ "$CI_COMMIT_BRANCH" = "main" ]; then + DESTINATIONS="$DESTINATIONS --destination git.example.com/org/web:v${BASE_VERSION}-rc.${BUILD_ID}" + DESTINATIONS="$DESTINATIONS --destination git.example.com/org/web:testing" + fi + if [ -n "$CI_COMMIT_TAG" ]; then + DESTINATIONS="$DESTINATIONS --destination git.example.com/org/web:$CI_COMMIT_TAG" + fi + /kaniko/executor --context . --dockerfile src/web/Dockerfile $DESTINATIONS + when: + - branch: [main] + event: [push, manual, tag] + depends_on: [build] + + # === Package Linking === + link-packages: + image: alpine:3 + environment: + GITEA_TOKEN: + from_secret: gitea_token + commands: + - apk add --no-cache curl + - sleep 10 + - | + set -e + link_package() { + PKG="$$1" + for attempt in 1 2 3; do + STATUS=$$(curl -s -o /dev/null -w "%{http_code}" -X POST \ + -H "Authorization: token $$GITEA_TOKEN" \ + "https://git.example.com/api/v1/packages/org/container/$$PKG/-/link/repo") + if [ "$$STATUS" = "201" ] || [ "$$STATUS" = "204" ] || [ "$$STATUS" = "400" ]; then + echo "Linked $$PKG ($$STATUS)" + return 0 + elif [ $$attempt -lt 3 ]; then + sleep 5 + else + echo "FAILED: $$PKG ($$STATUS)" + return 1 + fi + done + } + link_package "api" + link_package "web" + when: + - branch: [main] + event: [push, manual, tag] + depends_on: + - docker-build-api + - docker-build-web +``` + +## Checklist: Adding CI/CD to a Project + +1. **Verify Dockerfiles exist** for each service that needs an image +2. **Create Woodpecker secrets** (`gitea_username`, `gitea_token`) in the Woodpecker UI +3. **Verify Gitea token scope** includes `package:write` +4. **Add Docker build steps** to `.woodpecker.yml` using the Kaniko template above +5. **Add package linking step** after all Docker builds +6. **Update `docker-compose.yml`** to reference registry images instead of local builds: + ```yaml + image: git.example.com/org/service@${IMAGE_DIGEST} + ``` +7. **Test on a short-lived non-main branch first** — open a PR and verify quality gates before merging to `main` +8. **Verify images appear** in Gitea Packages tab after successful pipeline + +## Post-Merge CI Monitoring (Hard Rule) + +For source-code delivery, completion is not allowed at "PR opened" stage. + +Required sequence: + +1. Merge PR to `main` (squash) via Mosaic wrapper. +2. Monitor CI to terminal status: + ```bash + ~/.config/mosaic/tools/git/pr-ci-wait.sh -n + ``` +3. Require green status before claiming completion. +4. If CI fails, create remediation task(s) and continue until green. +5. If monitoring command fails, report blocker with the exact failed wrapper command and stop. + +Woodpecker note: + +- In Gitea + Woodpecker environments, commit status contexts generally reflect Woodpecker pipeline results. +- Always include CI run/status evidence in completion report. + +## Queue Guard Before Push/Merge (Hard Rule) + +Before pushing a branch or merging a PR, guard against overlapping project pipelines: + +```bash +~/.config/mosaic/tools/git/ci-queue-wait.sh --purpose push -B main +~/.config/mosaic/tools/git/ci-queue-wait.sh --purpose merge -B main +``` + +Behavior: + +- If pipeline state is running/queued/pending, wait until queue clears. +- If timeout or API/auth failure occurs, treat as `blocked`, report exact failed wrapper command, and stop. + +## Gitea as Unified Platform + +Gitea provides **multiple services in one**, eliminating the need for separate registry platforms: + +| Service | What Gitea Replaces | Registry URL | +| ---------------------- | ------------------------------ | ------------------------------------------------------ | +| **Git hosting** | GitHub/GitLab | `https://GITEA_HOST/org/repo` | +| **Container registry** | Harbor, Docker Hub | `docker pull GITEA_HOST/org/image:tag` | +| **npm registry** | Verdaccio, Artifactory | `https://GITEA_HOST/api/packages/org/npm/` | +| **PyPI registry** | Private PyPI/Artifactory | `https://GITEA_HOST/api/packages/org/pypi` | +| **Maven registry** | Nexus, Artifactory | `https://GITEA_HOST/api/packages/org/maven` | +| **NuGet registry** | Azure Artifacts, Artifactory | `https://GITEA_HOST/api/packages/org/nuget/index.json` | +| **Cargo registry** | crates.io mirrors, Artifactory | `https://GITEA_HOST/api/packages/org/cargo` | +| **Composer registry** | Private Packagist, Artifactory | `https://GITEA_HOST/api/packages/org/composer` | +| **Conan registry** | Artifactory Conan | `https://GITEA_HOST/api/packages/org/conan` | +| **Conda registry** | Anaconda Server, Artifactory | `https://GITEA_HOST/api/packages/org/conda` | +| **Generic registry** | Generic binary stores | `https://GITEA_HOST/api/packages/org/generic` | + +### Single Token, Multiple Services + +A Gitea token with `package:write` scope handles: + +- `git push` / `git pull` +- `docker push` / `docker pull` (container registry) +- `npm publish` / `npm install` (npm registry) +- `twine upload` / `pip install` (PyPI registry) +- package operations for Maven/NuGet/Cargo/Composer/Conan/Conda/Generic registries + +This means a single `gitea_token` secret in Woodpecker CI covers all CI/CD package operations. + +## Python Packages on Gitea PyPI + +For Python libraries and internal packages, use Gitea's built-in PyPI registry. + +### Publish (Local or CI) + +```bash +python -m pip install --upgrade build twine +python -m build +python -m twine upload \ + --repository-url "https://GITEA_HOST/api/packages/ORG/pypi" \ + --username "$GITEA_USERNAME" \ + --password "$GITEA_TOKEN" \ + dist/* +``` + +### Install (Consumer Projects) + +```bash +pip install \ + --extra-index-url "https://$GITEA_USERNAME:$GITEA_TOKEN@GITEA_HOST/api/packages/ORG/pypi/simple" \ + your-package-name +``` + +### Woodpecker Step (Python Publish) + +```yaml +publish-python-package: + image: python:3.12-slim + environment: + GITEA_USERNAME: + from_secret: gitea_username + GITEA_TOKEN: + from_secret: gitea_token + commands: + - python -m pip install --upgrade build twine + - python -m build + - python -m twine upload --repository-url https://GITEA_HOST/api/packages/ORG/pypi --username "$$GITEA_USERNAME" --password "$$GITEA_TOKEN" dist/* + when: + branch: [main] + event: [push] +``` + +### Architecture Simplification + +**Before (4 services):** + +``` +Gitea (git) + Harbor (containers) + Verdaccio (npm) + Private PyPI + ↓ separate auth ↓ separate auth ↓ extra auth ↓ extra auth + multiple tokens robot/service users npm-specific token pip/twine token + fragmented access fragmented RBAC fragmented RBAC fragmented RBAC +``` + +**After (1 service):** + +``` +Gitea (git + containers + npm + pypi) + ↓ unified secrets + 1 credentials model in CI + 1 backup target + unified RBAC via Gitea teams +``` + +## Migrating from Verdaccio to Gitea npm + +If a project currently uses Verdaccio (e.g., U-Connect at `npm.uscllc.net`), follow this migration checklist: + +### Migration Steps + +1. **Verify Gitea npm registry is accessible:** + + ```bash + curl -s https://GITEA_HOST/api/packages/ORG/npm/ | head -5 + ``` + +2. **Update `.npmrc` in project root:** + + ```ini + # Before (Verdaccio) + @uconnect:registry=https://npm.uscllc.net + + # After (Gitea) + @uconnect:registry=https://git.uscllc.com/api/packages/usc/npm/ + ``` + +3. **Update CI pipeline** — replace `npm_token` secret with `gitea_token`: + + ```yaml + # Uses same token as Docker push — no extra secret needed + echo "//GITEA_HOST/api/packages/ORG/npm/:_authToken=$$GITEA_TOKEN" > .npmrc + ``` + +4. **Re-publish existing packages** to Gitea registry: + + ```bash + # For each @scope/package + npm publish -w @scope/package --registry https://GITEA_HOST/api/packages/ORG/npm/ + ``` + +5. **Update consumer projects** — any project that `npm install`s from the old registry needs its `.npmrc` updated + +6. **Remove Verdaccio infrastructure:** + - Docker compose stack (`compose.verdaccio.yml`) + - Authentik OAuth provider/blueprints + - Verdaccio config files + - DNS entry for `npm.uscllc.net` (eventually) + +### What You Can Remove + +| Component | Location | Purpose (was) | +| -------------------- | ------------------------------------------- | --------------------------------------------- | +| Verdaccio compose | `compose.verdaccio.yml` | npm registry container | +| Verdaccio config | `config/verdaccio/` | Server configuration | +| Authentik blueprints | `config/authentik/blueprints/*/verdaccio-*` | OAuth integration | +| Verdaccio scripts | `scripts/verdaccio/` | Blueprint application | +| OIDC env vars | `.env` | `AUTHENTIK_VERDACCIO_*`, `VERDACCIO_OPENID_*` | + +## Troubleshooting + +### "unauthorized: authentication required" + +- Verify `gitea_username` and `gitea_token` secrets are set in Woodpecker +- Verify the token has `package:write` scope +- Check the registry hostname in `kaniko_setup` matches the Gitea instance + +### Kaniko build fails with "error building image" + +- Verify the Dockerfile path is correct relative to `--context` +- Check that multi-stage builds don't reference stages that don't exist +- Run `docker build` locally first to verify the Dockerfile works + +### Package linking returns 404 + +- Normal for recently pushed packages — the retry logic handles this +- If persistent: verify the package name matches exactly (case-sensitive) +- Check Gitea version is 1.24.0+ (package linking API requirement) + +### Images not visible in Gitea Packages + +- Linking may have failed — check the `link-packages` step logs +- Images are still usable via `docker pull` even without linking +- Link manually: Gitea UI > Packages > Select package > Link to repository + +### Pipeline runs Docker builds on pull requests + +- Verify `when` clause on Docker build steps restricts to `branch: [main]` +- Pull requests should only run quality gates, not build/push images diff --git a/packages/mosaic/framework/guides/CODE-REVIEW.md b/packages/mosaic/framework/guides/CODE-REVIEW.md new file mode 100755 index 0000000..10ac1e8 --- /dev/null +++ b/packages/mosaic/framework/guides/CODE-REVIEW.md @@ -0,0 +1,154 @@ +# Code Review Guide + +## Hard Requirement + +If an agent modifies source code, code review is REQUIRED before completion. +Do not mark code-change tasks done until review is completed and blockers are resolved or explicitly tracked. +If code/config/API contract/auth behavior changed and required docs are missing, this is a BLOCKER. +If tests pass but acceptance criteria are not verified by situational evidence, this is a BLOCKER. +If implementation diverges from `docs/PRD.md` or `docs/PRD.json` without PRD updates, this is a BLOCKER. + +Merge strategy enforcement (HARD RULE): + +- PR target for delivery is `main`. +- Direct pushes to `main` are prohibited. +- Merge to `main` MUST be squash-only. +- Use `~/.config/mosaic/tools/git/pr-merge.sh -n {PR_NUMBER} -m squash` (or PowerShell equivalent). + +## Review Checklist + +### 1. Correctness + +- [ ] Code does what the issue/PR description says +- [ ] Code aligns with active PRD requirements +- [ ] Acceptance criteria are mapped to concrete verification evidence +- [ ] Edge cases are handled +- [ ] Error conditions are managed properly +- [ ] No obvious bugs or logic errors + +### 2. Security + +- [ ] No hardcoded secrets or credentials +- [ ] Input validation at boundaries +- [ ] SQL injection prevention (parameterized queries) +- [ ] XSS prevention (output encoding) +- [ ] Authentication/authorization checks present +- [ ] Sensitive data not logged +- [ ] Secrets follow Vault structure (see `docs/vault-secrets-structure.md`) + +### 2a. OWASP Coverage (Required) + +- [ ] OWASP Top 10 categories were reviewed for change impact +- [ ] Access control checks verified on protected actions +- [ ] Cryptographic handling validated (keys, hashing, TLS assumptions) +- [ ] Injection risks reviewed for all untrusted inputs +- [ ] Security misconfiguration risks reviewed (headers, CORS, defaults) +- [ ] Dependency/component risk reviewed (known vulnerable components) +- [ ] Authentication/session flows reviewed for failure paths +- [ ] Logging/monitoring preserves detection without leaking sensitive data + +### 3. Testing + +- [ ] Tests exist for new functionality +- [ ] Tests cover happy path AND error cases +- [ ] Situational tests cover all impacted change surfaces (primary gate) +- [ ] Tests validate required behavior/outcomes, not only internal implementation details +- [ ] TDD was applied when required by `~/.config/mosaic/guides/QA-TESTING.md` +- [ ] Coverage meets 85% minimum +- [ ] Tests are readable and maintainable +- [ ] No flaky tests introduced + +### 4. Code Quality + +- [ ] Follows Google Style Guide for the language +- [ ] Functions are focused and reasonably sized +- [ ] No unnecessary complexity +- [ ] DRY - no significant duplication +- [ ] Clear naming for variables and functions +- [ ] No dead code or commented-out code + +### 4a. TypeScript Strict Typing (see `TYPESCRIPT.md`) + +- [ ] **NO `any` types** — explicit types required everywhere +- [ ] **NO lazy `unknown`** — only for error catches with immediate narrowing +- [ ] **Explicit return types** on all exported/public functions +- [ ] **Explicit parameter types** — never implicit any +- [ ] **No type assertions** (`as Type`) — use type guards instead +- [ ] **No non-null assertions** (`!`) — use proper null handling +- [ ] **Interfaces for objects** — not inline types +- [ ] **Discriminated unions** for variant types +- [ ] **DTO files used at boundaries** — module/API contracts are in `*.dto.ts`, not inline payload types + +### 5. Documentation + +- [ ] Complex logic has explanatory comments +- [ ] Required docs updated per `~/.config/mosaic/guides/DOCUMENTATION.md` +- [ ] Public APIs are documented +- [ ] Private/internal APIs are documented +- [ ] API input/output schemas are documented +- [ ] API permissions/auth requirements are documented +- [ ] Site map updates are present when navigation changed +- [ ] README updated if needed +- [ ] Breaking changes noted + +### 6. Performance + +- [ ] No obvious N+1 queries +- [ ] No blocking operations in hot paths +- [ ] Resource cleanup (connections, file handles) +- [ ] Reasonable memory usage + +### 7. Dependencies + +- [ ] No deprecated packages +- [ ] No unnecessary new dependencies +- [ ] Dependency versions pinned appropriately + +## Review Process + +Use `~/.config/mosaic/templates/docs/DOCUMENTATION-CHECKLIST.md` whenever code/API/auth/infra changes are present. + +### Getting Context + +```bash +# List the issue being addressed +~/.config/mosaic/tools/git/issue-list.sh -i {issue-number} + +# View the changes +git diff main...HEAD +``` + +### Providing Feedback + +- Be specific: point to exact lines/files +- Explain WHY something is problematic +- Suggest alternatives when possible +- Distinguish between blocking issues and suggestions +- Be constructive, not critical of the person + +### Feedback Categories + +- **Blocker**: Must fix before merge (security, bugs, test failures) +- **Should Fix**: Important but not blocking (code quality, minor issues) +- **Suggestion**: Optional improvements (style preferences, nice-to-haves) +- **Question**: Seeking clarification + +### Review Comment Format + +``` +[BLOCKER] Line 42: SQL injection vulnerability +The user input is directly interpolated into the query. +Use parameterized queries instead: +`db.query("SELECT * FROM users WHERE id = ?", [userId])` + +[SUGGESTION] Line 78: Consider extracting to helper +This pattern appears in 3 places. A shared helper would reduce duplication. +``` + +## After Review + +1. Update issue with review status +2. If changes requested, assign back to author +3. If approved, note approval in issue comments +4. For merges, ensure CI passes first +5. Merge PR to `main` with squash strategy only diff --git a/packages/mosaic/framework/guides/DOCUMENTATION.md b/packages/mosaic/framework/guides/DOCUMENTATION.md new file mode 100644 index 0000000..22adbfa --- /dev/null +++ b/packages/mosaic/framework/guides/DOCUMENTATION.md @@ -0,0 +1,132 @@ +# Documentation Standard (MANDATORY) + +This guide defines REQUIRED documentation behavior for all Mosaic projects. +If code, API contracts, auth, or infrastructure changes, documentation updates are REQUIRED before completion. + +## Hard Rules + +1. Documentation is a delivery gate. Missing required documentation is a BLOCKER. +2. `docs/PRD.md` or `docs/PRD.json` is REQUIRED as the project requirements source before coding begins. +3. API documentation is OpenAPI-first. `docs/API/OPENAPI.yaml` (or `.json`) is the canonical API contract. +4. Public and private/internal endpoints MUST be documented. +5. API input and output schemas MUST be documented. +6. API authentication and permissions MUST be documented per endpoint. +7. A current site map MUST exist at `docs/SITEMAP.md`. +8. Documentation updates MUST be committed in the same logical change set as the code/API change. +9. Generated publishing output (Docusaurus/VitePress/MkDocs artifacts) is not canonical unless the project explicitly declares it canonical. +10. `docs/` root MUST stay clean. Reports and working artifacts MUST be stored in dedicated subdirectories, not dumped at `docs/` root. + +## Required Documentation Structure + +```text +docs/ + PRD.md (or PRD.json) + TASKS.md (active orchestrator tracking, when orchestrator is used) + SITEMAP.md + USER-GUIDE/ + ADMIN-GUIDE/ + DEVELOPER-GUIDE/ + API/ + OPENAPI.yaml + ENDPOINTS.md + scratchpads/ + reports/ + tasks/ + releases/ + templates/ (optional) +``` + +Minimum requirements: + +- `docs/PRD.md` or `docs/PRD.json`: authoritative requirements source for implementation and testing. +- `docs/USER-GUIDE/`: End-user workflows, feature behavior, common troubleshooting. +- `docs/ADMIN-GUIDE/`: Configuration, deployment, operations, incident/recovery procedures. +- `docs/DEVELOPER-GUIDE/`: Architecture, local setup, contribution/testing workflow, design constraints. +- `docs/API/OPENAPI.yaml`: API SSOT for all HTTP endpoints. +- `docs/API/ENDPOINTS.md`: Human-readable index for API endpoints, permissions, and change notes. +- `docs/SITEMAP.md`: Navigation index for all user/admin/developer/API documentation pages. +- `docs/reports/`: Review outputs, QA automation reports, deferrals, and audit artifacts. +- `docs/tasks/`: Archived task snapshots and orchestrator learnings. +- `docs/releases/`: Release notes and release-specific documentation. +- `docs/scratchpads/`: Active task-level working notes. + +## Root Hygiene Rule (MANDATORY) + +Allowed root documentation files are intentionally limited: + +1. `docs/PRD.md` or `docs/PRD.json` +2. `docs/TASKS.md` (active milestone only, when task orchestration is in use) +3. `docs/SITEMAP.md` +4. `docs/README.md` (optional index) + +All other docs MUST be placed in scoped folders (`docs/reports/`, `docs/tasks/`, `docs/releases/`, `docs/scratchpads/`, `docs/API/`, guide books). + +## Artifact Placement Rules + +| Artifact Type | REQUIRED Location | +| ------------------------------------------ | ---------------------------------------- | +| Code review reports, QA reports, audits | `docs/reports//` | +| Deferred error lists / unresolved findings | `docs/reports/deferred/` | +| Archived milestone task snapshots | `docs/tasks/` | +| Orchestrator learnings JSON | `docs/tasks/orchestrator-learnings.json` | +| Release notes | `docs/releases/` | +| Active scratchpads | `docs/scratchpads/` | + +## API Documentation Contract (OpenAPI-First) + +For every API endpoint, documentation MUST include: + +1. visibility: `public` or `private/internal` +2. method and path +3. endpoint purpose +4. request/input schema +5. response/output schema(s) +6. auth method and required permission/role/scope +7. error status codes and behavior + +If OpenAPI cannot fully express an internal constraint, document it in `docs/API/ENDPOINTS.md`. + +## Book/Chapter/Page Structure + +Use this structure for every guide: + +1. Book: one root guide folder (`USER-GUIDE`, `ADMIN-GUIDE`, `DEVELOPER-GUIDE`) +2. Chapter: one subdirectory per topic area +3. Page: one focused markdown file per concern + +Required index files: + +1. `docs/USER-GUIDE/README.md` +2. `docs/ADMIN-GUIDE/README.md` +3. `docs/DEVELOPER-GUIDE/README.md` + +Each index file MUST link to all chapters and pages in that book. + +## Situational Documentation Matrix + +| Change Surface | REQUIRED Documentation Updates | +| ---------------------------------------------- | ----------------------------------------------------------- | +| New feature or behavior change | User guide + developer guide + sitemap | +| API endpoint added/changed/removed | OpenAPI + API endpoint index + sitemap | +| Auth/RBAC/permission change | API auth/permission docs + admin guide + developer guide | +| Database schema/migration change | Developer guide + admin operational notes if runbook impact | +| CI/CD or deployment change | Admin guide + developer guide | +| Incident, recovery, or security control change | Admin guide runbook + security notes + sitemap | + +## Publishing Target Rule (MANDATORY) + +If the user does not specify documentation publishing target, the agent MUST ask: + +1. Publish in-app (embedded docs) +2. Publish on external docs platform (for example: Docusaurus, VitePress, MkDocs) + +Default behavior before publishing decision: + +- Keep canonical docs in-repo under `docs/`. +- Do not assume external publishing platform. + +## Completion Gate + +You MUST NOT declare completion until all required documentation updates are done. + +Use `~/.config/mosaic/templates/docs/DOCUMENTATION-CHECKLIST.md` as the final gate. diff --git a/packages/mosaic/framework/guides/E2E-DELIVERY.md b/packages/mosaic/framework/guides/E2E-DELIVERY.md new file mode 100644 index 0000000..4dd48cc --- /dev/null +++ b/packages/mosaic/framework/guides/E2E-DELIVERY.md @@ -0,0 +1,210 @@ +# E2E Delivery Procedure (MANDATORY) + +This guide is REQUIRED for all agent sessions. + +## 0. Mode Handshake (Before Any Action) + +First response MUST declare mode before tool calls or implementation steps: + +1. Orchestration mission: `Now initiating Orchestrator mode...` +2. Implementation mission: `Now initiating Delivery mode...` +3. Review-only mission: `Now initiating Review mode...` + +## 1. PRD Gate (Before Coding) + +1. Ensure `docs/PRD.md` or `docs/PRD.json` exists before coding. +2. Load `~/.config/mosaic/guides/PRD.md`. +3. Prepare/update PRD from user input and available project context. +4. If requirements are missing: + - proceed with best-guess assumptions by default, + - mark each assumption with `ASSUMPTION:` and rationale, + - escalate only when uncertainty is high-impact and cannot be bounded safely. +5. Treat PRD as the requirement source for implementation, testing, and review. + +## 1a. Tracking Gate (Before Coding) + +1. For non-trivial work, `docs/TASKS.md` MUST exist before coding. +2. If `docs/TASKS.md` is missing, create it from `~/.config/mosaic/templates/docs/TASKS.md.template`. +3. Detect provider first via `~/.config/mosaic/tools/git/detect-platform.sh`. +4. For issue/PR/milestone operations, use Mosaic wrappers first (`~/.config/mosaic/tools/git/*.sh`). +5. If external git provider is available (Gitea/GitHub/GitLab), create or update issue(s) before coding. +6. Record provider issue reference(s) in `docs/TASKS.md` (example: `#123`). +7. If no external provider is available, use internal task refs in `docs/TASKS.md` (example: `TASKS:T1`). +8. Scratchpad MUST reference both task ID and issue/internal ref. + +## 2. Intake and Scope + +> **COMPLEXITY TRAP WARNING:** Intake applies to ALL tasks regardless of perceived complexity. "Simple" tasks (commit, push, deploy) have caused the most severe framework violations because agents skip intake when they pattern-match a task as mechanical. The procedure is unconditional. + +1. Define scope, constraints, and acceptance criteria. +2. Identify affected surfaces (API, DB, UI, infra, auth, CI/CD, docs). +3. **Deployment surface check (MANDATORY if task involves deploy, images, or containers):** Before ANY build or deploy action, check for CI/CD pipeline config (`.woodpecker/`, `.woodpecker.yml`, `.github/workflows/`). If pipelines exist, CI is the canonical build path — manual `docker build`/`docker push` is forbidden. Load `~/.config/mosaic/guides/CI-CD-PIPELINES.md` immediately. +4. Identify required guides and load them before implementation. +5. For code/API/auth/infra changes, load `~/.config/mosaic/guides/DOCUMENTATION.md`. +6. Determine budget constraints: + - if the user provided a plan limit or token budget, treat it as a HARD cap, + - if budget is unknown, derive a working budget from estimates and runtime limits, then continue autonomously. +7. Record budget assumptions and caps in the scratchpad before implementation starts. +8. Track estimated vs used tokens per logical unit and adapt strategy to remain inside budget. +9. If projected usage exceeds budget, auto-reduce scope/parallelism first; escalate only if cap still cannot be met. + +## 2a. Steered Autonomy (Lights-Out) + +1. Agent owns delivery end-to-end: planning, coding, testing, review, PR/repo operations, release/tag, and deployment (when in scope). +2. Human intervention is escalation-only; do not pause for routine approvals or handoffs. +3. Continue execution until completion criteria are met or an escalation trigger is hit. + +## 3. Scratchpad Requirement + +1. Create a task-specific scratchpad before implementation. +2. Record: + - objective + - plan + - progress checkpoints + - tests run + - risks/blockers + - final verification evidence + +## 4. Embedded Execution Cycle (MANDATORY) + +For implementation work, you MUST run this cycle in order: + +1. `plan` - map PRD requirements to concrete implementation steps. +2. `code` - implement one logical unit. +3. `test` - run required baseline and situational checks for that unit. +4. `review` - perform independent code review on the current delta. +5. `remediate` - fix all findings and any test failures. +6. `review` - re-review remediated changes until blockers are cleared. +7. `commit` - commit only when the logical unit passes tests and review. +8. `pre-push queue guard` - before pushing, wait for running/queued project pipelines to clear: `~/.config/mosaic/tools/git/ci-queue-wait.sh --purpose push`. +9. `push` - push immediately after queue guard passes. +10. `PR integration` - if external git provider is available, create/update PR to `main` and merge with required strategy via Mosaic wrappers. +11. `pre-merge queue guard` - before merging PR, wait for running/queued project pipelines to clear: `~/.config/mosaic/tools/git/ci-queue-wait.sh --purpose merge`. +12. `CI/pipeline verification` - wait for terminal CI status and require green before completion (`~/.config/mosaic/tools/git/pr-ci-wait.sh` for PR-based workflow). +13. `issue closure` - close linked external issue (or close internal `docs/TASKS.md` task ref when provider is unavailable). +14. `greenfield situational test` - validate required user flows in a clean environment/startup path (post-merge for trunk workflow changes). +15. `deploy + post-deploy validation` - when deployment is in scope, deploy to configured target and run post-deploy health/smoke checks. +16. `repeat` - continue until all acceptance criteria are complete. + +### Post-PR Hard Gate (Execute Sequentially, No Exceptions) + +1. `~/.config/mosaic/tools/git/ci-queue-wait.sh --purpose merge -B main` +2. `~/.config/mosaic/tools/git/pr-merge.sh -n -m squash` +3. `~/.config/mosaic/tools/git/pr-ci-wait.sh -n ` +4. `~/.config/mosaic/tools/git/issue-close.sh -i ` (or close internal `docs/TASKS.md` ref when no provider exists) +5. If any step fails: set status `blocked`, report the exact failed wrapper command, and stop. +6. Do not ask the human to perform routine merge/close operations. +7. Do not claim completion before step 4 succeeds. + +### Forbidden Anti-Patterns + +**PR/Merge:** + +1. Do NOT stop at "PR created" or "PR updated". +2. Do NOT ask "should I merge?" for routine delivery PRs. +3. Do NOT ask "should I close the issue?" after merge + green CI. + +**Build/Deploy:** 4. Do NOT run `docker build` or `docker push` locally to deploy images when CI/CD pipelines exist in the repository. CI is the ONLY canonical build path. 5. Do NOT skip intake and surface identification because a task "seems simple." This is the #1 cause of framework violations. 6. Do NOT deploy without first verifying whether CI/CD pipelines exist (`.woodpecker/`, `.woodpecker.yml`, `.github/workflows/`). If they exist, use them. 7. If you are about to run `docker build` and have NOT loaded `ci-cd-pipelines.md`, STOP — you are violating the framework. + +If any step fails, you MUST remediate and re-run from the relevant step before proceeding. +If push-queue/merge-queue/PR merge/CI/issue closure fails, status is `blocked` (not complete) and you MUST report the exact failed wrapper command. + +## 5. Testing Priority Model + +Use this order of priority: + +1. Situational tests are the PRIMARY gate and MUST prove changed behavior meets requirements. +2. Baseline tests are REQUIRED safety checks and MUST run for all software changes. +3. TDD is risk-based and REQUIRED only for specific high-risk change types. + +## 6. Mandatory Test Baseline + +For all software changes, you MUST run baseline checks applicable to the repo/toolchain: + +1. lint (or equivalent static checks) +2. type checks (if language/tooling supports it) +3. unit tests for changed logic +4. integration tests for changed boundaries + +## 7. Situational Testing Matrix (PRIMARY GATE) + +Run additional tests based on what changed: + +| Change Surface | Required Situational Tests | +| ---------------------------- | ----------------------------------------------------------------------------- | +| Authentication/authorization | auth failure-path tests, permission boundary tests, token/session validation | +| Database schema/migrations | migration up/down validation, rollback safety, data integrity checks | +| API contract changes | backward compatibility checks, consumer-impact tests, contract tests | +| Frontend/UI workflow changes | end-to-end flow tests, accessibility sanity checks, state transition checks | +| CI/CD or deployment changes | pipeline execution validation, artifact integrity checks, rollback path check | +| Security-sensitive logic | abuse-case tests, input validation fuzzing/sanitization checks | +| Performance-critical path | baseline comparison, regression threshold checks | + +## 8. Risk-Based TDD Requirement + +TDD is REQUIRED for: + +1. bug fixes (write a reproducer test first) +2. security/auth/permission logic changes +3. critical business logic and data-mutation rules + +TDD is RECOMMENDED (not mandatory) for low-risk UI, copy, styling, and mechanical refactors. +If TDD is skipped for a non-required case, record the rationale in the scratchpad. + +## 9. Mandatory Code Review Gate + +If you modify source code, you MUST run an independent code review before completion. + +1. Use automated review tooling when available. +2. If automated tooling is unavailable, run manual review using `~/.config/mosaic/guides/CODE-REVIEW.md`. +3. Any blocker or critical finding MUST be fixed or tracked as an explicit remediation task before closure. + +## 10. Mandatory Documentation Gate + +For code/API/auth/infra changes, documentation updates are REQUIRED before completion. + +1. Apply the standard in `~/.config/mosaic/guides/DOCUMENTATION.md`. +2. Update required docs in the same logical change set as implementation. +3. Complete `~/.config/mosaic/templates/docs/DOCUMENTATION-CHECKLIST.md`. +4. If publish platform is unspecified, ask the user to choose in-app or external platform before publishing. +5. Missing required documentation is a BLOCKER. + +## 11. Completion Gate (All Required) + +You MUST satisfy all items before completion: + +1. Acceptance criteria met. +2. Baseline tests passed. +3. Situational tests passed (primary gate), including required greenfield situational validation. +4. PRD is current and implementation is aligned with PRD. +5. Acceptance criteria mapped to verification evidence. +6. Code review completed for source code changes. +7. Required documentation updates completed and reviewed. +8. Scratchpad updated with evidence. +9. Known risks documented. +10. No unresolved blocker hidden. +11. If deployment is in scope, deployment target, release version, and post-deploy verification evidence are documented. +12. `docs/TASKS.md` status and issue/internal references are updated to match delivered work. +13. If source code changed and external provider is available: PR merged to `main` (squash), with merge evidence recorded. +14. CI/pipeline status is terminal green for the merged PR/head commit. +15. Linked external issue is closed (or internal task ref is closed when no provider exists). +16. If any of items 13-15 fail due access/tooling, report `blocked` with exact failed wrapper command and do not claim completion. + +## 12. Review and Reporting + +Completion report MUST include: + +1. what changed +2. PRD alignment summary +3. acceptance criteria to evidence mapping +4. what was tested (baseline + situational) +5. what was reviewed (code review scope) +6. what documentation was updated +7. command-level evidence summary +8. residual risks +9. deployment and post-deploy verification summary (if in scope) +10. explicit pass/fail status +11. tracking summary (`docs/TASKS.md` updates and issue/internal refs) +12. PR lifecycle summary (PR number, merge commit, merge method) +13. CI/pipeline summary (run/check URL, terminal status) +14. issue closure summary (issue number/ref and close evidence) diff --git a/packages/mosaic/framework/guides/FRONTEND.md b/packages/mosaic/framework/guides/FRONTEND.md new file mode 100644 index 0000000..d656d8a --- /dev/null +++ b/packages/mosaic/framework/guides/FRONTEND.md @@ -0,0 +1,91 @@ +# Frontend Development Guide + +## Before Starting + +1. Check assigned issue in git repo: `~/.config/mosaic/tools/git/issue-list.sh -a @me` +2. Create scratchpad: `docs/scratchpads/{issue-number}-{short-name}.md` +3. Review existing components and patterns in the codebase + +## Development Standards + +### Framework Conventions + +- Follow project's existing framework patterns (React, Vue, Svelte, etc.) +- Use existing component library/design system if present +- Maintain consistent file structure with existing code + +### Styling + +- Use project's established styling approach (CSS modules, Tailwind, styled-components, etc.) +- Follow existing naming conventions for CSS classes +- Ensure responsive design unless explicitly single-platform + +### State Management + +- Use project's existing state management solution +- Keep component state local when possible +- Document any new global state additions + +### Accessibility + +- Include proper ARIA labels +- Ensure keyboard navigation works +- Test with screen reader considerations +- Maintain color contrast ratios (WCAG 2.1 AA minimum) + +## Testing Requirements (TDD) + +1. Write tests BEFORE implementation +2. Minimum 85% coverage +3. Test categories: + - Unit tests for utility functions + - Component tests for UI behavior + - Integration tests for user flows + +### Test Patterns + +```javascript +// Component test example structure +describe('ComponentName', () => { + it('renders without crashing', () => {}); + it('handles user interaction correctly', () => {}); + it('displays error states appropriately', () => {}); + it('is accessible', () => {}); +}); +``` + +## Code Style + +- Follow Google JavaScript/TypeScript Style Guide +- **TypeScript: Follow `~/.config/mosaic/guides/TYPESCRIPT.md` — MANDATORY** +- Use ESLint/Prettier configuration from project +- Prefer functional components over class components (React) +- TypeScript strict mode is REQUIRED, not optional + +### TypeScript Quick Rules (see TYPESCRIPT.md for full guide) + +- **NO `any`** — define explicit types always +- **NO lazy `unknown`** — only for error catches and external data with validation +- **Explicit return types** on all exported functions +- **Explicit parameter types** always +- **Interface for props** — never inline object types +- **Event handlers** — use proper React event types + +## Commit Format + +``` +feat(#123): Add user profile component + +- Implement avatar display +- Add edit mode toggle +- Include form validation + +Refs #123 +``` + +## Before Completing + +1. Run full test suite +2. Verify build succeeds +3. Update scratchpad with completion notes +4. Reference issue in commit: `Fixes #N` or `Refs #N` diff --git a/packages/mosaic/framework/guides/INFRASTRUCTURE.md b/packages/mosaic/framework/guides/INFRASTRUCTURE.md new file mode 100644 index 0000000..adb4f03 --- /dev/null +++ b/packages/mosaic/framework/guides/INFRASTRUCTURE.md @@ -0,0 +1,339 @@ +# Infrastructure & DevOps Guide + +## Before Starting + +1. Check assigned issue: `~/.config/mosaic/tools/git/issue-list.sh -a @me` +2. Create scratchpad: `docs/scratchpads/{issue-number}-{short-name}.md` +3. Review existing infrastructure configuration + +## Vault Secrets Management + +**CRITICAL**: Follow canonical Vault structure for ALL secrets. + +### Structure + +``` +{mount}/{service}/{component}/{secret-name} + +Examples: +- secret-prod/postgres/database/app +- secret-prod/redis/auth/default +- secret-prod/authentik/admin/token +``` + +### Environment Mounts + +- `secret-dev/` - Development environment +- `secret-staging/` - Staging environment +- `secret-prod/` - Production environment + +### Standard Field Names + +- Credentials: `username`, `password` +- Tokens: `token` +- OAuth: `client_id`, `client_secret` +- Connection strings: `url`, `host`, `port` + +See `docs/vault-secrets-structure.md` for complete reference. + +## Container Standards + +### Dockerfile Best Practices + +```dockerfile +# Use specific version tags +FROM node:20-alpine + +# Create non-root user +RUN addgroup -S app && adduser -S app -G app + +# Set working directory +WORKDIR /app + +# Copy dependency files first (layer caching) +COPY package*.json ./ +RUN npm ci --only=production + +# Copy application code +COPY --chown=app:app . . + +# Switch to non-root user +USER app + +# Use exec form for CMD +CMD ["node", "server.js"] +``` + +### Container Security + +- Use minimal base images (alpine, distroless) +- Run as non-root user +- Don't store secrets in images +- Scan images for vulnerabilities +- Pin dependency versions + +## Kubernetes/Docker Compose + +### Resource Limits + +Always set resource limits to prevent runaway containers: + +```yaml +resources: + requests: + memory: '128Mi' + cpu: '100m' + limits: + memory: '256Mi' + cpu: '500m' +``` + +### Health Checks + +```yaml +livenessProbe: + httpGet: + path: /health + port: 8080 + initialDelaySeconds: 10 + periodSeconds: 5 + +readinessProbe: + httpGet: + path: /ready + port: 8080 + initialDelaySeconds: 5 + periodSeconds: 3 +``` + +## CI/CD Pipelines + +### Pipeline Stages + +1. **Lint**: Code style and static analysis +2. **Test**: Unit and integration tests +3. **Build**: Compile and package +4. **Scan**: Security and vulnerability scanning +5. **Deploy**: Environment-specific deployment + +### Pipeline Security + +- Use secrets management (not hardcoded) +- Pin action/image versions +- Implement approval gates for production +- Audit pipeline access + +## Steered-Autonomous Deployment (Hard Rule) + +In lights-out mode, the agent owns deployment end-to-end when deployment is in scope. +The human is escalation-only for missing access, hard policy conflicts, or irreversible risk. + +### Deployment Target Selection + +1. Use explicit target from `docs/PRD.md` / `docs/PRD.json` or `docs/DEPLOYMENT.md`. +2. If unspecified, infer from existing project config/integration. +3. If multiple targets exist, choose the target already wired in CI/CD and document rationale. + +### Supported Targets + +- **Portainer**: Deploy via `~/.config/mosaic/tools/portainer/stack-redeploy.sh`, then verify with `stack-status.sh`. +- **Coolify**: Deploy via `~/.config/mosaic/tools/coolify/deploy.sh -u `, then verify with `service-status.sh`. +- **Vercel**: Deploy via `vercel` CLI or connected Git integration, then verify preview/production URL health. +- **Other SaaS providers**: Use provider CLI/API/runbook with the same validation and rollback gates. + +### Coolify API Operations + +```bash +# List projects and services +~/.config/mosaic/tools/coolify/project-list.sh +~/.config/mosaic/tools/coolify/service-list.sh + +# Check service status +~/.config/mosaic/tools/coolify/service-status.sh -u + +# Set env vars (takes effect on next deploy) +~/.config/mosaic/tools/coolify/env-set.sh -u -k KEY -v VALUE + +# Deploy +~/.config/mosaic/tools/coolify/deploy.sh -u +``` + +**Known Coolify Limitations:** + +- FQDN updates on compose sub-apps not supported via API (DB workaround required) +- Compose files must be base64-encoded in `docker_compose_raw` field +- Magic variables (`SERVICE_FQDN_*`) require list-style env syntax, not dict-style +- Rate limit: 200 requests per interval + +### Cloudflare DNS Operations + +Use the Cloudflare tools for any DNS configuration: pointing domains at services, adding TXT verification records, managing MX records, etc. + +**Multi-instance support**: Credentials support named instances (e.g. `personal`, `work`). A `default` key in credentials.json determines which instance is used when `-a` is omitted. Pass `-a ` to target a specific account. + +```bash +# List all zones (domains) in the account +~/.config/mosaic/tools/cloudflare/zone-list.sh [-a instance] + +# List DNS records for a zone (accepts zone name or ID) +~/.config/mosaic/tools/cloudflare/record-list.sh -z [-t type] [-n name] + +# Create a DNS record +~/.config/mosaic/tools/cloudflare/record-create.sh -z -t -n -c [-p] [-l ttl] [-P priority] + +# Update a DNS record (requires record ID from record-list) +~/.config/mosaic/tools/cloudflare/record-update.sh -z -r -t -n -c [-p] + +# Delete a DNS record +~/.config/mosaic/tools/cloudflare/record-delete.sh -z -r +``` + +**Flag reference:** + +| Flag | Purpose | +| ---- | ----------------------------------------------------------------------- | +| `-z` | Zone name (e.g. `mosaicstack.dev`) or 32-char zone ID | +| `-a` | Named Cloudflare instance (omit for default) | +| `-t` | Record type: `A`, `AAAA`, `CNAME`, `MX`, `TXT`, `SRV`, etc. | +| `-n` | Record name: short (`app`) or FQDN (`app.example.com`) | +| `-c` | Record content/value (IP, hostname, TXT string, etc.) | +| `-r` | Record ID (from `record-list.sh` output) | +| `-p` | Enable Cloudflare proxy (orange cloud) — omit for DNS-only (grey cloud) | +| `-l` | TTL in seconds (default: `1` = auto) | +| `-P` | Priority for MX/SRV records | +| `-f` | Output format: `table` (default) or `json` | + +**Common workflows:** + +```bash +# Point a new subdomain at a server (proxied through Cloudflare) +~/.config/mosaic/tools/cloudflare/record-create.sh \ + -z example.com -t A -n myapp -c 203.0.113.10 -p + +# Add a TXT record for domain verification (never proxied) +~/.config/mosaic/tools/cloudflare/record-create.sh \ + -z example.com -t TXT -n _verify -c "verification=abc123" + +# Check what records exist before making changes +~/.config/mosaic/tools/cloudflare/record-list.sh -z example.com -t CNAME + +# Update an existing record (get record ID from record-list first) +~/.config/mosaic/tools/cloudflare/record-update.sh \ + -z example.com -r -t A -n myapp -c 10.0.0.5 -p +``` + +**DNS + Deployment integration**: When deploying a new service via Coolify or Portainer that needs a public domain, the typical sequence is: + +1. Create the DNS record pointing at the host IP (with `-p` for Cloudflare proxy if desired) +2. Deploy the service via Coolify/Portainer +3. Verify the domain resolves and the service is reachable + +**Proxy (`-p`) guidance:** + +- Use proxy (orange cloud) for web services — provides CDN, DDoS protection, and hides origin IP +- Skip proxy (grey cloud) for non-HTTP services (mail, SSH), wildcard records, or when the service handles its own TLS termination and needs direct client IP visibility +- Proxy is NOT compatible with non-standard ports outside Cloudflare's supported range + +### Stack Health Check + +Verify all infrastructure services are reachable: + +```bash +~/.config/mosaic/tools/health/stack-health.sh +``` + +### Image Tagging and Promotion (Hard Rule) + +For containerized deployments: + +1. Build immutable image tags: `sha-` and `v{base-version}-rc.{build}`. +2. Use mutable environment tags only as pointers: `testing`, optional `staging`, and `prod`. +3. Deploy by immutable digest, not by mutable tag alone. +4. Promote the exact tested digest between environments (no rebuild between testing and prod). +5. Do not use `latest` or `dev` as deployment references. + +Blue-green is the default strategy for production promotion. +Canary is allowed only when automated SLO/error-rate gates and auto-rollback triggers are implemented. + +### Post-Deploy Validation (REQUIRED) + +1. Health endpoints return expected status. +2. Critical smoke tests pass in target environment. +3. Running version and digest match the promoted release candidate. +4. Observability signals (errors/latency) are within expected thresholds. + +### Rollback Rule + +If post-deploy validation fails: + +1. Execute rollback/redeploy-safe path immediately. +2. Mark deployment as blocked in `docs/TASKS.md`. +3. Record failure evidence and next remediation step in scratchpad and release notes. + +### Registry Retention and Cleanup + +Cleanup MUST be automated. + +- Keep all final release tags (`vX.Y.Z`) indefinitely. +- Keep active environment digests (`prod`, `testing`, and active blue/green slots). +- Keep recent RC tags (`vX.Y.Z-rc.N`) based on retention window. +- Remove stale `sha-*` and RC tags outside retention window if they are not actively deployed. + +## Monitoring & Logging + +### Logging Standards + +- Use structured logging (JSON) +- Include correlation IDs +- Log at appropriate levels (ERROR, WARN, INFO, DEBUG) +- Never log sensitive data + +### Metrics to Collect + +- Request latency (p50, p95, p99) +- Error rates +- Resource utilization (CPU, memory) +- Business metrics + +### Alerting + +- Define SLOs (Service Level Objectives) +- Alert on symptoms, not causes +- Include runbook links in alerts +- Avoid alert fatigue + +## Testing Infrastructure + +### Test Categories + +1. **Unit tests**: Terraform/Ansible logic +2. **Integration tests**: Deployed resources work together +3. **Smoke tests**: Critical paths after deployment +4. **Chaos tests**: Failure mode validation + +### Infrastructure Testing Tools + +- Terraform: `terraform validate`, `terraform plan` +- Ansible: `ansible-lint`, molecule +- Kubernetes: `kubectl dry-run`, kubeval +- General: Terratest, ServerSpec + +## Commit Format + +``` +chore(#67): Configure Redis cluster + +- Add Redis StatefulSet with 3 replicas +- Configure persistence with PVC +- Add Vault secret for auth password + +Refs #67 +``` + +## Before Completing + +1. Validate configuration syntax +2. Run infrastructure tests +3. Test in dev/staging first +4. Document any manual steps required +5. Update scratchpad and close issue diff --git a/packages/mosaic/framework/guides/MEMORY.md b/packages/mosaic/framework/guides/MEMORY.md new file mode 100644 index 0000000..a020e37 --- /dev/null +++ b/packages/mosaic/framework/guides/MEMORY.md @@ -0,0 +1,51 @@ +# Memory and Retention Rules + +## Primary Memory Layer: OpenBrain + +**OpenBrain is the canonical shared memory for all Mosaic agents across all harnesses and sessions.** + +Use the `capture` MCP tool (or REST `POST /v1/thoughts`) to store: + +- Discovered gotchas and workarounds +- Architectural decisions and rationale +- Project state and context for handoffs +- Anything a future agent should know + +Use `search` or `recent` at session start to load prior context before acting. + +This is not optional. An agent that uses local file-based memory instead of OpenBrain is a broken agent — its knowledge is invisible to every other agent on the platform. + +## Hard Rules + +1. Agent learnings MUST go to OpenBrain — not to any file-based memory location. +2. You MUST NOT write to runtime-native memory silos (they are write-blocked by hook). +3. Active execution state belongs in project `docs/` — not in memory files. +4. `~/.config/mosaic/memory/` is for mosaic framework technical notes only, not project knowledge. + +## Runtime-Native Memory Silos (WRITE-BLOCKED) + +These locations are blocked by PreToolUse hooks. Attempting to write there fails at the tool level. + +| Runtime | Blocked silo | Use instead | +| ----------- | ---------------------------------- | ------------------- | +| Claude Code | `~/.claude/projects/*/memory/*.md` | OpenBrain `capture` | +| Codex | Runtime session memory | OpenBrain `capture` | +| OpenCode | Runtime session memory | OpenBrain `capture` | + +MEMORY.md files may only contain behavioral guardrails that must be injected at load-path — not knowledge. + +## Project Continuity Files (MANDATORY) + +| File | Purpose | Location | +| -------------------------------- | ----------------------------------------- | --------------------------- | +| `docs/PRD.md` or `docs/PRD.json` | Source of requirements | Project `docs/` | +| `docs/TASKS.md` | Task tracking, milestones, issues, status | Project `docs/` | +| `docs/scratchpads/.md` | Task-specific working memory | Project `docs/scratchpads/` | +| `AGENTS.md` | Project-local patterns and conventions | Project root | + +## How the Block Works + +`~/.config/mosaic/tools/qa/prevent-memory-write.sh` is registered as a `PreToolUse` hook in +`~/.claude/settings.json`. It intercepts Write/Edit/MultiEdit calls and rejects any targeting +`~/.claude/projects/*/memory/*.md` before the tool executes. Exit code 2 blocks the call and +the agent sees a message directing it to OpenBrain instead. diff --git a/packages/mosaic/framework/guides/ORCHESTRATOR-LEARNINGS.md b/packages/mosaic/framework/guides/ORCHESTRATOR-LEARNINGS.md new file mode 100644 index 0000000..fb8938a --- /dev/null +++ b/packages/mosaic/framework/guides/ORCHESTRATOR-LEARNINGS.md @@ -0,0 +1,127 @@ +# Orchestrator Learnings (Universal) + +> Cross-project heuristic adjustments based on observed variance data. +> +> **Note:** This file contains generic patterns only. Project-specific evidence is stored in each project's `docs/tasks/orchestrator-learnings.json`. + +## Task Type Multipliers + +Apply these multipliers to base estimates from `ORCHESTRATOR.md`: + +| Task Type | Base Estimate | Multiplier | Confidence | Samples | Last Updated | +| --------------------- | ---------------- | ---------- | ---------- | ------- | ------------ | +| STYLE_FIX | 3-5K | 0.64 | MEDIUM | n=1 | 2026-02-05 | +| BULK_CLEANUP | file_count × 550 | 1.0 | MEDIUM | n=2 | 2026-02-05 | +| GUARD_ADD | 5-8K | 1.0 | LOW | n=0 | - | +| SECURITY_FIX | 8-12K | 2.5 | LOW | n=0 | - | +| AUTH_ADD | 15-25K | 1.0 | HIGH | n=1 | 2026-02-05 | +| REFACTOR | 10-15K | 1.0 | LOW | n=0 | - | +| TEST_ADD | 15-25K | 1.0 | LOW | n=0 | - | +| ERROR_HANDLING | 8-12K | 2.3 | MEDIUM | n=1 | 2026-02-05 | +| CONFIG_DEFAULT_CHANGE | 5-10K | 1.8 | MEDIUM | n=1 | 2026-02-05 | +| INPUT_VALIDATION | 5-8K | 1.7 | MEDIUM | n=1 | 2026-02-05 | + +## Phase Factors + +Apply to all estimates based on task position in milestone: + +| Phase Position | Factor | Rationale | +| ----------------- | ------ | -------------------------- | +| Early (tasks 1-3) | 1.45 | Codebase learning overhead | +| Mid (tasks 4-7) | 1.25 | Pattern recognition phase | +| Late (tasks 8+) | 1.10 | Established patterns | + +## Estimation Formula + +``` +Final Estimate = Base Estimate × Type Multiplier × Phase Factor × TDD Overhead + +Where: +- Base Estimate: From ORCHESTRATOR.md task type table +- Type Multiplier: From table above (default 1.0) +- Phase Factor: 1.45 / 1.25 / 1.10 based on position +- TDD Overhead: 1.20 if tests required +``` + +## Known Patterns + +### BULK_CLEANUP + +**Pattern:** Multi-file cleanup tasks are severely underestimated. + +**Why:** Iterative testing across many files, cascading fixes, and debugging compound the effort. + +**Observed:** +112% to +276% variance when using fixed estimates. + +**Recommendation:** Use `file_count × 550` instead of fixed estimate. + +### ERROR_HANDLING + +**Pattern:** Error handling changes that modify type interfaces cascade through the codebase. + +**Why:** Adding fields to result types requires updating all callers, error messages, and tests. + +**Observed:** +131% variance. + +**Multiplier:** 2.3x base estimate when type interfaces are modified. + +### CONFIG_DEFAULT_CHANGE + +**Pattern:** Config default changes require more test coverage than expected. + +**Why:** Security-sensitive defaults need validation tests, warning tests, and edge case coverage. + +**Observed:** +80% variance. + +**Multiplier:** 1.8x when config changes need security validation. + +### INPUT_VALIDATION + +**Pattern:** Security input validation with allowlists is more complex than simple validation. + +**Why:** Comprehensive allowlists (e.g., OAuth error codes), encoding requirements, and security tests add up. + +**Observed:** +70% variance. + +**Multiplier:** 1.7x when security allowlists are involved. + +### STYLE_FIX + +**Pattern:** Pure formatting fixes are faster than estimated when isolated. + +**Observed:** -36% variance. + +**Multiplier:** 0.64x for isolated style-only fixes. + +## Changelog + +| Date | Change | Samples | Confidence | +| ---------- | ------------------------------------------- | ------- | ---------- | +| 2026-02-05 | Added BULK_CLEANUP category | n=2 | MEDIUM | +| 2026-02-05 | Added STYLE_FIX multiplier 0.64 | n=1 | MEDIUM | +| 2026-02-05 | Confirmed AUTH_ADD heuristic accurate | n=1 | HIGH | +| 2026-02-05 | Added ERROR_HANDLING multiplier 2.3x | n=1 | MEDIUM | +| 2026-02-05 | Added CONFIG_DEFAULT_CHANGE multiplier 1.8x | n=1 | MEDIUM | +| 2026-02-05 | Added INPUT_VALIDATION multiplier 1.7x | n=1 | MEDIUM | + +## Update Protocol + +**Graduated Autonomy:** + +| Phase | Condition | Action | +| ---------------------- | ----------------------------------------- | -------------------------------------------- | +| **Now** | All proposals | Human review required | +| **After 3 milestones** | <30% change, n≥3 samples, HIGH confidence | Auto-update allowed | +| **Mature** | All changes | Auto with notification, revert on regression | + +**Validation Before Update:** + +1. Minimum 3 samples for same task type +2. Standard deviation < 30% of mean +3. Outliers (>2σ) excluded +4. New formula must not increase variance on historical data + +## Where to Find Project-Specific Data + +- **Project learnings:** `/docs/tasks/orchestrator-learnings.json` +- **Cross-project metrics:** `jarvis-brain/data/orchestrator-metrics.json` diff --git a/packages/mosaic/framework/guides/ORCHESTRATOR-PROTOCOL.md b/packages/mosaic/framework/guides/ORCHESTRATOR-PROTOCOL.md new file mode 100644 index 0000000..3566ef8 --- /dev/null +++ b/packages/mosaic/framework/guides/ORCHESTRATOR-PROTOCOL.md @@ -0,0 +1,268 @@ +# Orchestrator Protocol — Mission Lifecycle Guide + +> **Operational guide for agent sessions.** Distilled from the full specification at +> `jarvis-brain/docs/protocols/ORCHESTRATOR-PROTOCOL.md` (1,066 lines). +> +> Load this guide when: active mission detected, multi-milestone orchestration, mission continuation. +> Load `ORCHESTRATOR.md` for per-session execution protocol (planning, coding, review, commit cycle). + +--- + +## 1. Relationship to ORCHESTRATOR.md + +| Concern | Guide | +| -------------------------------------------------------------------- | ----------------- | +| How to execute within a session (plan, code, test, review, commit) | `ORCHESTRATOR.md` | +| How to manage a mission across sessions (resume, continue, handoff) | **This guide** | +| Both guides are active simultaneously during orchestration missions. | + +--- + +## 2. Mission Manifest + +**Location:** `docs/MISSION-MANIFEST.md` +**Owner:** Orchestrator (sole writer) +**Template:** `~/.config/mosaic/templates/docs/MISSION-MANIFEST.md.template` + +The manifest is the persistent document tracking full mission scope, status, milestones, and session history. It survives session death. + +### Update Rules + +- Update **Phase** when transitioning (Intake → Planning → Execution → Continuation → Completion) +- Update **Current Milestone** when starting a new milestone +- Update **Progress** after each milestone completion +- Append to **Session History** at session start and end +- Update **Status** to `completed` only when ALL success criteria are verified + +### Hard Rule + +The manifest is the source of truth for mission scope. If the manifest says a milestone is done, it is done. If it says remaining, it remains. + +--- + +## 3. Scratchpad Protocol + +**Location:** `docs/scratchpads/{mission-id}.md` +**Template:** `~/.config/mosaic/templates/docs/mission-scratchpad.md.template` + +### Rules + +1. **First action** — Before ANY planning or coding, write the mission prompt to the scratchpad +2. **Append-only** — NEVER delete or overwrite previous entries +3. **Session log** — Record session start, tasks done, and outcome at session end +4. **Decisions** — Record all planning decisions with rationale +5. **Corrections** — Record course corrections from human or coordinator +6. **Never deleted** — Scratchpads survive mission completion (archival reference) + +--- + +## 4. TASKS.md as Control Plane + +**Location:** `docs/TASKS.md` +**Owner:** Orchestrator (sole writer). Workers read but NEVER modify. + +### Table Schema + +```markdown +| id | status | milestone | description | pr | notes | +``` + +### Status Values + +`not-started` → `in-progress` → `done` (or `blocked` / `failed`) + +### Planning Tasks Are First-Class + +Include explicit planning tasks (e.g., `PLAN-001: Break down milestone into tasks`). These count toward progress. + +### Post-Merge Tasks Are Explicit + +Include verification tasks after merge: CI check, deployment verification, Playwright test. Don't assume they happen automatically. + +--- + +## 5. Session Resume Protocol + +When starting a session and an active mission is detected, follow this checklist: + +### Detection (5-point check) + +1. `docs/MISSION-MANIFEST.md` exists → read Phase, Current Milestone, Progress +2. `docs/scratchpads/*.md` exists → read latest scratchpad for decisions and corrections +3. `docs/TASKS.md` exists → read task state (what's done, what's next) +4. Git state → current branch, open PRs, recent commits +5. Provider state → open issues, milestone status (if accessible) + +### Resume Procedure + +1. Read the mission manifest FIRST +2. Read the scratchpad for session history and corrections +3. Read TASKS.md for current task state +4. Identify the next `not-started` or `in-progress` task +5. Continue execution from that task +6. Update Session History in the manifest + +### Dirty State Recovery + +| State | Recovery | +| ------------------------ | ------------------------------------------------------------------- | +| Dirty git working tree | Stash changes, log stash ref in scratchpad, resume clean | +| Open PR in bad state | Check PR status, close if broken, re-create if needed | +| Half-created issues | Audit issues against TASKS.md, reconcile | +| Tasks marked in-progress | Check if work was committed; if so, mark done; if not, restart task | + +### Hard Rule + +Session state is NEVER automatically deleted. The coordinator (human or automated) must explicitly request cleanup. + +--- + +## 6. Mission Continuation + +When a milestone completes and more milestones remain: + +### Agent Handoff (at ~55-60% context) + +If context usage is high, produce a handoff message: + +1. Update TASKS.md with final task statuses +2. Update mission manifest with session results +3. Append session summary to scratchpad +4. Commit all state files +5. The coordinator will generate a continuation prompt for the next session + +### Continuation Prompt and Capsule Format + +The coordinator generates this (via `mosaic coord continue`) and writes a machine-readable capsule at `.mosaic/orchestrator/next-task.json`: + +``` +## Continuation Mission +Continue **{mission}** from existing state. +- Read docs/MISSION-MANIFEST.md for scope and status +- Read docs/scratchpads/{id}.md for decisions +- Read docs/TASKS.md for current state +- Continue from task {next-task-id} +``` + +### Between Sessions (r0 manual) + +1. Agent stops (expected — this is the confirmed stamina limitation) +2. Human runs `mosaic coord mission` to check status +3. Human runs `mosaic coord continue` to generate continuation prompt +4. Human launches new session and pastes the prompt +5. New agent reads manifest, scratchpad, TASKS.md and continues + +### Between Sessions (r0 assisted) + +Use `mosaic coord run` to remove copy/paste steps: + +1. Agent stops +2. Human runs `mosaic coord run [--claude|--codex]` +3. Coordinator regenerates continuation prompt + `next-task.json` +4. Coordinator launches selected runtime with scoped kickoff context +5. New session resumes from next task + +--- + +## 7. Failure Taxonomy Quick Reference + +| Code | Type | Recovery | +| ---- | ---------------------- | ----------------------------------------------------- | +| F1 | Premature Stop | Continuation prompt → new session (most common) | +| F2 | Context Exhaustion | Handoff message → new session | +| F3 | Session Crash | Check git state → `mosaic coord resume` → new session | +| F4 | Error Spiral | Kill session, mark task blocked, skip to next | +| F5 | Quality Gate Failure | Create QA remediation task | +| F6 | Infrastructure Failure | Pause, retry when service recovers | +| F7 | False Completion | Append correction to scratchpad, relaunch | +| F8 | Scope Drift | Kill session, relaunch with scratchpad ref | +| F9 | Subagent Failure | Orchestrator retries or creates remediation | +| F10 | Deadlock | Escalate to human | + +### F1: Premature Stop — Detailed Recovery + +This is the confirmed, most common failure. Every session will eventually trigger F1. + +1. Session ends with tasks remaining in TASKS.md +2. Run `mosaic coord mission` — verify milestone status +3. If milestone complete: verify CI green, deployed, issues closed +4. Run `mosaic coord continue` — generates scoped continuation prompt +5. Launch new session, paste prompt +6. New session reads state and continues from next pending task + +--- + +## 8. r0 Manual Coordinator Process + +In r0, the Coordinator is Jason + shell scripts. No daemon. No automation. + +### Commands + +| Command | Purpose | +| --------------------------------------------------- | ------------------------------------------------- | ------------------------------------------------ | +| `mosaic coord init --name "..." --milestones "..."` | Initialize a new mission | +| `mosaic coord mission` | Show mission progress dashboard | +| `mosaic coord status` | Check if agent session is still running | +| `mosaic coord continue` | Generate continuation prompt for next session | +| `mosaic coord run [--claude | --codex]` | Generate continuation context and launch runtime | +| `mosaic coord resume` | Crash recovery (detect dirty state, generate fix) | +| `mosaic coord resume --clean-lock` | Clear stale session lock after review | + +### Typical Workflow + +``` +init → launch agent → [agent works] → agent stops → +status → mission → run → repeat +``` + +--- + +## 9. Operational Checklist + +### Pre-Mission + +- [ ] Mission initialized: `mosaic coord init` +- [ ] docs/MISSION-MANIFEST.md exists with scope and milestones +- [ ] docs/TASKS.md scaffolded +- [ ] docs/scratchpads/{id}.md scaffolded +- [ ] Success criteria defined in manifest + +### Session Start + +- [ ] Read manifest → know phase, milestone, progress +- [ ] Read scratchpad → know decisions, corrections, history +- [ ] Read TASKS.md → know what's done and what's next +- [ ] Write session start to scratchpad +- [ ] Update Session History in manifest + +### Planning Gate (Hard Gate — No Coding Until Complete) + +- [ ] Milestones created in provider (Gitea/GitHub) +- [ ] Issues created for all milestone tasks +- [ ] TASKS.md populated with all planned tasks (including planning + verification tasks) +- [ ] All planning artifacts committed and pushed + +### Per-Task + +- [ ] Update task status to `in-progress` in TASKS.md +- [ ] Execute task following ORCHESTRATOR.md cycle +- [ ] Update task status to `done` (or `blocked`/`failed`) +- [ ] Commit, push + +### Milestone Completion + +- [ ] All milestone tasks in TASKS.md are `done` +- [ ] CI/pipeline green +- [ ] PR merged to `main` +- [ ] Issues closed +- [ ] Update manifest: milestone status → completed +- [ ] Update scratchpad: session log entry +- [ ] If deployment target: verify accessible + +### Mission Completion + +- [ ] ALL milestones completed +- [ ] ALL success criteria verified with evidence +- [ ] manifest status → completed +- [ ] Final scratchpad entry with completion evidence +- [ ] Release tag created and pushed (if applicable) diff --git a/packages/mosaic/framework/guides/ORCHESTRATOR.md b/packages/mosaic/framework/guides/ORCHESTRATOR.md new file mode 100644 index 0000000..bfd9792 --- /dev/null +++ b/packages/mosaic/framework/guides/ORCHESTRATOR.md @@ -0,0 +1,1175 @@ +# Autonomous Orchestrator Guide + +When spawning workers, include skill loading in the kickstart: + +```bash +claude -p "Read ~/.config/mosaic/skills/nestjs-best-practices/SKILL.md then implement..."codex exec "Read ~/.config/mosaic/skills/nestjs-best-practices/SKILL.md then implement..." +``` + +#### **MANDATORY** + +- You MUST establish the requirements for a task, or series of tasks, before coding begins. +- You MUST ensure `docs/PRD.md` or `docs/PRD.json` exists before worker coding starts. +- You MUST use sequential-thinking to properly plan out tasks, milestones, epics, and PRD requirements prior to orchestrating agents. +- You MUST track tasks at the project level in docs/TASKS.md. +- You MUST keep the TASKS.md file updated with agent and tasks statuses. +- You MUST keep `docs/` root clean. Reports and working artifacts MUST be stored in scoped folders (`docs/reports/`, `docs/tasks/`, `docs/releases/`, `docs/scratchpads/`). +- You MUST enforce plan/token usage budgets when provided, and adapt orchestration strategy to remain within limits. +- You MUST enforce trunk workflow: workers branch from `main`, PR target is `main`, direct push to `main` is forbidden, and PR merges to `main` are squash-only. +- You MUST operate in steered-autonomy mode: human intervention is escalation-only; do not require the human to write code, review code, or manage PR/repo workflow. +- You MUST NOT declare task or issue completion until PR is merged, CI/pipeline is terminal green, and linked issue is closed (or internal TASKS ref is closed when provider is unavailable). +- Mosaic orchestration rules OVERRIDE runtime-default caution for routine push/merge/issue-close actions required by this workflow. +- Do NOT ask "should I merge?" or "should I close the issue?" for routine delivery flow after gates pass. + +## Overview + +## Session Start Handshake (Hard Rule) + +Before any orchestration actions, the first response MUST be: + +`Now initiating Orchestrator mode...` + +Then proceed with orchestration bootstrap steps. + +The orchestrator **cold-starts** on any project with just a review report location and minimal kickstart. It autonomously: + +1. Prepares/updates project PRD (`docs/PRD.md` or `docs/PRD.json`) from user input and available project context +2. Parses review reports to extract findings +3. Categorizes findings into phases by severity +4. Estimates token usage per task +5. Creates phase issues in the configured git provider (Gitea/GitHub/GitLab) +6. Bootstraps `docs/TASKS.md` from scratch +7. Coordinates completion using worker agents +8. Enforces documentation completion gates for code/API/auth/infra changes + +**Key principle:** The orchestrator is the **sole writer** of `docs/TASKS.md`. Worker agents execute tasks and report results — they never modify the tracking file. + +--- + +## Orchestrator Boundaries (CRITICAL) + +**The orchestrator NEVER:** + +- Edits source code directly (_.ts, _.tsx, _.js, _.py, etc.) +- Runs quality gates itself (that's the worker's job) +- Makes commits containing code changes +- "Quickly fixes" something to save time — this is how drift starts + +**The orchestrator ONLY:** + +- Reads/writes `docs/TASKS.md` +- Reads/writes `docs/tasks/orchestrator-learnings.json` +- Delegates ALL code changes to workers (native subagent tool when available, otherwise Mosaic matrix rail) +- Parses worker JSON results +- Commits task tracking updates (tasks.md, learnings) +- Outputs status reports and handoff messages + +**If you find yourself about to edit source code, STOP.** +Spawn a worker instead. No exceptions. No "quick fixes." + +**Worker Limits:** + +- Maximum **2 parallel workers** at any time +- Wait for at least one worker to complete before spawning more +- This optimizes token usage and reduces context pressure + +## Delegation Mode Selection + +Choose one delegation mode at session start: + +1. **Native subagent mode** (preferred when runtime supports it) +2. **Matrix rail mode** (fallback when native subagents/background tasks are unavailable) + +Matrix rail mode commands: + +```bash +~/.config/mosaic/bin/mosaic-orchestrator-matrix-cycle +~/.config/mosaic/bin/mosaic-orchestrator-run --poll-sec 10 +~/.config/mosaic/bin/mosaic-orchestrator-sync-tasks --apply +~/.config/mosaic/bin/mosaic-orchestrator-drain +``` + +In Matrix rail mode, keep `docs/TASKS.md` as canonical project tracking and use +`.mosaic/orchestrator/` for deterministic worker dispatch state. + +--- + +## Bootstrap Templates + +Use templates from `jarvis-brain/docs/templates/` to scaffold tracking files: + +```bash +# Set environment variables +export PROJECT="project-name" +export MILESTONE="0.0.1" +export CURRENT_DATETIME=$(date -Iseconds) +export TASK_PREFIX="PR-SEC" +export PHASE_ISSUE="#1" +export PHASE_BRANCH="fix/security" + +# Copy templates +TEMPLATES=~/src/jarvis-brain/docs/templates + +# Create PRD if missing (before coding begins) +[[ -f docs/PRD.md || -f docs/PRD.json ]] || cp ~/.config/mosaic/templates/docs/PRD.md.template docs/PRD.md + +# Create TASKS.md (then populate with findings) +envsubst < $TEMPLATES/orchestrator/tasks.md.template > docs/TASKS.md + +# Create learnings tracking +mkdir -p docs/tasks docs/reports/deferred +envsubst < $TEMPLATES/orchestrator/orchestrator-learnings.json.template > docs/tasks/orchestrator-learnings.json + +# Create review report structure (if doing new review) +$TEMPLATES/reports/review-report-scaffold.sh codebase-review +``` + +Milestone versioning (HARD RULE): + +- Pre-MVP milestones MUST start at `0.0.1`. +- Pre-MVP progression MUST remain in `0.0.x` (`0.0.2`, `0.0.3`, ...). +- `0.1.0` is reserved for MVP release. +- You MUST NOT start pre-MVP planning at `0.1.0`. + +Branch and merge strategy (HARD RULE): + +- Workers use short-lived task branches from `origin/main`. +- Worker task branches merge back via PR to `main` only. +- Direct pushes to `main` are prohibited. +- PR merges to `main` MUST use squash merge. + +**Available templates:** + +| Template | Purpose | +| --------------------------------------------------- | ------------------------------- | +| `orchestrator/tasks.md.template` | Task tracking table with schema | +| `orchestrator/orchestrator-learnings.json.template` | Variance tracking | +| `orchestrator/phase-issue-body.md.template` | Git provider issue body | +| `orchestrator/compaction-summary.md.template` | 60% checkpoint format | +| `reports/review-report-scaffold.sh` | Creates report directory | +| `scratchpad.md.template` | Per-task working document | + +See `jarvis-brain/docs/templates/README.md` for full documentation. + +--- + +## Phase 1: Bootstrap + +### Step 0: Prepare PRD (Required Before Coding) + +Before creating tasks or spawning workers: + +1. Ensure `docs/PRD.md` or `docs/PRD.json` exists. +2. Build/update PRD from user input and available project context. +3. If requirements are missing, proceed with best-guess assumptions by default and mark each guessed requirement with `ASSUMPTION:` in PRD. +4. Escalate only when uncertainty is high-impact and cannot be safely bounded with rollback-ready defaults. +5. Do NOT start worker coding tasks until this step is complete. + +### Step 1: Parse Review Reports + +Review reports typically follow this structure: + +``` +docs/reports/{report-name}/ +├── 00-executive-summary.md # Start here - overview and counts +├── 01-security-review.md # Security findings with IDs like SEC-* +├── 02-code-quality-review.md # Code quality findings like CQ-* +├── 03-qa-test-coverage.md # Test coverage gaps like TEST-* +└── ... +``` + +**Extract findings by looking for:** + +- Finding IDs (e.g., `SEC-API-1`, `CQ-WEB-3`, `TEST-001`) +- Severity labels: Critical, High, Medium, Low +- Affected files/components (use for `repo` column) +- Specific line numbers or code patterns + +**Parse each finding into:** + +``` +{ + id: "SEC-API-1", + severity: "critical", + title: "Brief description", + component: "api", // For repo column + file: "path/to/file.ts", // Reference for worker + lines: "45-67" // Specific location +} +``` + +### Step 2: Categorize into Phases + +Map severity to phases: + +| Severity | Phase | Focus | Branch Pattern | +| -------- | ----- | --------------------------------------- | ------------------- | +| Critical | 1 | Security vulnerabilities, data exposure | `fix/security` | +| High | 2 | Security hardening, auth gaps | `fix/security` | +| Medium | 3 | Code quality, performance, bugs | `fix/code-quality` | +| Low | 4 | Tests, documentation, cleanup | `fix/test-coverage` | + +**Within each phase, order tasks by:** + +1. Blockers first (tasks that unblock others) +2. Same-file tasks grouped together +3. Simpler fixes before complex ones + +### Step 3: Estimate Token Usage + +Use these heuristics based on task type: + +| Task Type | Estimate | Examples | +| --------------------- | -------- | ----------------------------------------- | +| Single-line fix | 3-5K | Typo, wrong operator, missing null check | +| Add guard/validation | 5-8K | Add auth decorator, input validation | +| Fix error handling | 8-12K | Proper try/catch, error propagation | +| Refactor pattern | 10-15K | Replace KEYS with SCAN, fix memory leak | +| Add new functionality | 15-25K | New service method, new component | +| Write tests | 15-25K | Unit tests for untested service | +| Complex refactor | 25-40K | Architectural change, multi-file refactor | + +**Adjust estimates based on:** + +- Number of files affected (+5K per additional file) +- Test requirements (+5-10K if tests needed) +- Documentation needs (+2-3K if docs needed) + +### Step 3b: Budget Guardrail (HARD RULE) + +Before creating dependencies or dispatching workers: + +1. Determine budget cap: + - Use explicit user plan/token cap if provided. + - If no cap is provided, derive a soft cap from estimates and runtime constraints, then continue autonomously. +2. Calculate projected total from `estimate` column and record cap in task notes/scratchpad. +3. Apply dispatch mode by budget pressure: + - `<70%` of cap projected: normal orchestration (up to 2 workers). + - `70-90%` of cap projected: conservative mode (1 worker, tighter scope, no exploratory tasks). + - `>90%` of cap projected: freeze new worker starts; triage remaining work with user. +4. If projected usage exceeds cap, first reduce scope/parallelism automatically. + If cap still cannot be met, STOP and ask user to: + - reduce scope, or + - split into phases, or + - approve a higher budget. + +### Step 4: Determine Dependencies + +**Automatic dependency rules:** + +1. All tasks in Phase N depend on the Phase N-1 verification task +2. Tasks touching the same file should be sequential (earlier blocks later) +3. Auth/security foundation tasks block tasks that rely on them +4. Each phase ends with a verification task that depends on all phase tasks + +**Create verification tasks:** + +- `{PREFIX}-SEC-{LAST}`: Phase 1 verification (run security tests) +- `{PREFIX}-HIGH-{LAST}`: Phase 2 verification +- `{PREFIX}-CQ-{LAST}`: Phase 3 verification +- `{PREFIX}-TEST-{LAST}`: Phase 4 verification (final quality gates) + +### Step 5: Create Phase Issues (Gitea, GitHub, or GitLab) + +You MUST create ONE issue per phase in the configured external git provider. + +Milestone binding rule: + +- When the project is pre-MVP, issue milestones MUST use a `0.0.x` milestone. +- `0.1.0` MUST be used only for the MVP release milestone. + +Provider options: + +1. Gitea (preferred when available) via Mosaic helper: + +```bash +~/.config/mosaic/tools/git/issue-create.sh \ + -t "Phase 1: Critical Security Fixes" \ + -b "$(cat <<'EOF' +## Findings + +- SEC-API-1: Description +- SEC-WEB-2: Description +- SEC-ORCH-1: Description + +## Acceptance Criteria + +- [ ] All critical findings remediated +- [ ] Quality gates passing +- [ ] Required documentation updates complete +- [ ] No new regressions +EOF +)" \ + -l "security,critical" \ + -m "{milestone-name}" +``` + +2. GitHub (if repository uses GitHub): + +```bash +gh issue create \ + --title "Phase 1: Critical Security Fixes" \ + --body-file /tmp/phase-1-body.md \ + --label "security,critical" \ + --milestone "{milestone-name}" +``` + +3. GitLab (if repository uses GitLab): + +```bash +glab issue create \ + --title "Phase 1: Critical Security Fixes" \ + --description-file /tmp/phase-1-body.md \ + --label "security,critical" \ + --milestone "{milestone-name}" +``` + +No external provider fallback (HARD RULE): + +- If Gitea/GitHub/GitLab is unavailable, you MUST track phase-level milestones and issue equivalents directly in `docs/TASKS.md`. +- In this mode, the `issue` column MUST use internal refs (example: `TASKS:P1`, `TASKS:P2`). +- You MUST keep `docs/TASKS.md` as the complete system of record for tasks, milestones, and issue status. + +**Capture issue references** — you'll link tasks to these. + +### Step 6: Create docs/TASKS.md + +Create the file with this exact schema: + +```markdown +# Tasks + +| id | status | description | issue | repo | branch | depends_on | blocks | agent | started_at | completed_at | estimate | used | notes | +| ---------------- | ----------- | ---------------------------- | ----- | ---- | ------------ | ---------- | ---------------- | ----- | ---------- | ------------ | -------- | ---- | ----- | +| {PREFIX}-SEC-001 | not-started | SEC-API-1: Brief description | #{N} | api | fix/security | | {PREFIX}-SEC-002 | | | | 8K | | | +``` + +**Column definitions:** + +| Column | Format | Purpose | +| -------------- | ------------------------------------------------------------------------------- | ---------------------------------------------------------------- | +| `id` | `{PREFIX}-{CAT}-{NNN}` | Unique task ID (e.g., MS-SEC-001) | +| `status` | `not-started` \| `in-progress` \| `done` \| `failed` \| `blocked` \| `needs-qa` | Current state | +| `description` | `{FindingID}: Brief summary` | What to fix | +| `issue` | `#NNN` or `TASKS:Pn` | Provider issue ref (phase-level) or internal TASKS milestone ref | +| `repo` | Workspace name | `api`, `web`, `orchestrator`, etc. | +| `branch` | Branch name | `fix/security`, `fix/code-quality`, etc. | +| `depends_on` | Comma-separated IDs | Must complete first | +| `blocks` | Comma-separated IDs | Tasks waiting on this | +| `agent` | Agent identifier | Assigned worker (fill when claiming) | +| `started_at` | ISO 8601 | When work began | +| `completed_at` | ISO 8601 | When work finished | +| `estimate` | `5K`, `15K`, etc. | Predicted token usage | +| `used` | `4.2K`, `12.8K`, etc. | Actual usage (fill on completion) | +| `notes` | free text | Review results, PR/CI/issue closure evidence, blocker commands | + +Status rule: + +- `done` is allowed only after PR merge + green CI + issue/ref closure for source-code tasks. + +**Category prefixes:** + +- `SEC` — Security (Phase 1-2) +- `HIGH` — High priority (Phase 2) +- `CQ` — Code quality (Phase 3) +- `TEST` — Test coverage (Phase 4) +- `PERF` — Performance (Phase 3) +- `DOC` — Documentation updates/gates + +### Step 6b: Add Documentation Tasks (MANDATORY) + +For each phase containing code/API/auth/infra work: + +1. Add explicit documentation tasks in `docs/TASKS.md` (or include docs in phase verification tasks). +2. Require completion of `~/.config/mosaic/templates/docs/DOCUMENTATION-CHECKLIST.md`. +3. Ensure phase acceptance criteria includes documentation completion. +4. Do not mark phase complete until documentation tasks are done. + +### Step 7: Commit Bootstrap + +```bash +git add docs/TASKS.md +git commit -m "chore(orchestrator): Bootstrap tasks.md from review report + +Parsed {N} findings into {M} tasks across {P} phases. +Estimated total: {X}K tokens." +git push +``` + +--- + +## Phase 2: Execution Loop + +```` +1. git pull --rebase +2. Read docs/TASKS.md +3. Find next task: status=not-started AND all depends_on are done +4. If no task available: + - All done? → Report success, run final retrospective, STOP + - Some blocked? → Report deadlock, STOP +5. Update tasks.md: status=in-progress, agent={identifier}, started_at={now} +6. Budget gate (before dispatch): + - Compute cumulative used + remaining estimate + - If projected total > budget cap: STOP and request user decision (reduce scope/phase/increase cap) + - If projected total is 70-90% of cap: run conservative mode (single worker) +7. Delegate worker task: + - native mode: spawn worker agent via runtime subagent/task primitive + - matrix mode: enqueue/consume task in `.mosaic/orchestrator/tasks.json` and run `mosaic-orchestrator-matrix-cycle` +8. Wait for worker completion +9. Parse worker result (JSON) +10. **Variance check**: Calculate (actual - estimate) / estimate × 100 + - If |variance| > 50%: Capture learning (see Learning & Retrospective) + - If |variance| > 100%: Flag as CRITICAL — review task classification +11. **Post-Coding Review** (see Phase 2b below) +12. **Documentation Gate**: Verify required docs were updated per `~/.config/mosaic/guides/DOCUMENTATION.md` + and checklist completed (`~/.config/mosaic/templates/docs/DOCUMENTATION-CHECKLIST.md`) when applicable. +13. **PR + CI + Issue Closure Gate** (HARD RULE for source-code tasks): + - Before merging, run queue guard: + `~/.config/mosaic/tools/git/ci-queue-wait.sh --purpose merge -B main` + - Ensure PR exists for the task branch (create/update via wrappers if needed): + `~/.config/mosaic/tools/git/pr-create.sh ... -B main` + - Merge via wrapper: + `~/.config/mosaic/tools/git/pr-merge.sh -n {PR_NUMBER} -m squash` + - Wait for terminal CI status: + `~/.config/mosaic/tools/git/pr-ci-wait.sh -n {PR_NUMBER}` + - Close linked issue after merge + green CI: + `~/.config/mosaic/tools/git/issue-close.sh -i {ISSUE_NUMBER}` + - If any wrapper command fails, mark task `blocked`, record the exact failed wrapper command, report blocker, and STOP. + - Do NOT stop at "PR created" or "PR merged pending CI". + - Do NOT claim completion before CI is green and issue/internal ref is closed. +14. Update tasks.md: status=done/failed/needs-qa/blocked, completed_at={now}, used={actual} +15. Recalculate budget position: + - cumulative used + - projected remaining from estimates + - total projected at completion +16. **Cleanup reports**: Remove processed report files for completed task + ```bash + # Find and remove reports matching the finding ID + find docs/reports/qa-automation/pending/ -name "*{finding_id}*" -delete 2>/dev/null || true + # If task failed, move reports to escalated/ instead + ``` +17. Commit + push: git add docs/TASKS.md .gitignore && git commit && git push +18. If phase verification task: Run phase retrospective, clean up all phase reports +19. Check context usage +20. If >= 55%: Output COMPACTION REQUIRED checkpoint, STOP, wait for user +21. Check budget usage: + - If projected total > cap: STOP and request user decision before new tasks + - If projected total is 70-90% of cap: continue in conservative mode +22. If < 55% context and within budget: Go to step 1 +23. After user runs /compact and says "continue": Go to step 1 +```` + +--- + +## Phase 2b: Post-Coding Review (MANDATORY) + +**CRITICAL:** After any worker completes a task that modifies source code, the orchestrator MUST run an independent review before marking the task as done. This catches bugs, security issues, and regressions that the worker missed. + +### When to Review + +Run review when the worker's result includes code changes (commits). Skip for tasks that only modify docs, config, or tracking files. + +### Step 1: Run Codex Review (Primary) + +```bash +# Navigate to the project directory +cd {project_path} + +# Code quality review +~/.config/mosaic/tools/codex/codex-code-review.sh -b {base_branch} -o /tmp/review-{task_id}.json + +# Security review +~/.config/mosaic/tools/codex/codex-security-review.sh -b {base_branch} -o /tmp/security-{task_id}.json +``` + +### Step 2: Parse Review Results + +```bash +# Check code review +CODE_BLOCKERS=$(jq '.stats.blockers // 0' /tmp/review-{task_id}.json) +CODE_VERDICT=$(jq -r '.verdict // "comment"' /tmp/review-{task_id}.json) + +# Check security review +SEC_CRITICAL=$(jq '.stats.critical // 0' /tmp/security-{task_id}.json) +SEC_HIGH=$(jq '.stats.high // 0' /tmp/security-{task_id}.json) +``` + +### Step 3: Decision Tree + +``` +IF Codex is unavailable (command not found, auth failure, API error): + → Use fallback review (Step 4) + +IF CODE_BLOCKERS > 0 OR SEC_CRITICAL > 0 OR SEC_HIGH > 0: + → Mark task as "needs-qa" in tasks.md + → Create a remediation task: + - ID: {task_id}-QA + - Description: Fix findings from review (list specific issues) + - depends_on: (none — it's a follow-up, not a blocker) + - Notes: Include finding titles and file locations + → Continue to next task (remediation task will be picked up in order) + +IF CODE_VERDICT == "request-changes" (but no blockers): + → Log should-fix findings in task notes + → Mark task as done (non-blocking suggestions) + → Consider creating a tech-debt issue for significant suggestions + +IF CODE_VERDICT == "approve" AND SEC_CRITICAL == 0 AND SEC_HIGH == 0: + → Mark task as done + → Log: "Review passed — no issues found" +``` + +### Step 4: Fallback Review (When Codex is Unavailable) + +If the `codex` CLI is not installed or authentication fails, use Claude's built-in review capabilities: + +````markdown +## Fallback: Spawn a Review Agent + +Use the Task tool to spawn a review subagent: + +## Prompt: + +## Independent Code Review + +Review the code changes on branch {branch} against {base_branch}. + +1. Run: `git diff {base_branch}...HEAD` +2. Review for: + - Correctness (bugs, logic errors, edge cases) + - Security (OWASP Top 10, secrets, injection) + - Testing (coverage, quality) + - Code quality (complexity, duplication) +3. Reference: ~/.config/mosaic/guides/CODE-REVIEW.md + +Report findings as JSON: + +```json +{ + "verdict": "approve|request-changes", + "blockers": 0, + "critical_security": 0, + "findings": [ + { + "severity": "blocker|should-fix|suggestion", + "title": "...", + "file": "...", + "description": "..." + } + ] +} +``` +```` + +--- + +``` + +### Review Timing Guidelines + +| Task Type | Review Required? | +|-----------|-----------------| +| Source code changes (*.ts, *.py, etc.) | **YES — always** | +| Configuration changes (*.yml, *.toml) | YES — security review only | +| Documentation changes (*.md) | No | +| Task tracking updates (tasks.md) | No | +| Test-only changes | YES — code review only | + +### Logging Review Results + +In the task notes column of tasks.md, append review results: + +``` + +Review: approve (0 blockers, 0 critical) | Codex 0.98.0 + +``` + +or: + +``` + +Review: needs-qa (1 blocker, 2 high) → QA task {task_id}-QA created + +````` + +--- + +## Worker Prompt Template + +Construct this from the task row and pass to worker via Task tool: + +````markdown +## Task Assignment: {id} + +**Description:** {description} +**Repository:** {project_path}/apps/{repo} +**Branch:** {branch} + +**Reference:** See `docs/reports/` for detailed finding description. Search for the finding ID. + +## Workflow + +1. Checkout branch: `git fetch origin && (git checkout {branch} || git checkout -b {branch} origin/main) && git rebase origin/main` +2. Read `docs/PRD.md` or `docs/PRD.json` and align implementation with PRD requirements +3. Read the finding details from the report +4. Implement the fix following existing code patterns +5. Run quality gates (ALL must pass — zero lint errors, zero type errors, all tests green): + ```bash + {quality_gates_command} +````` + +**MANDATORY:** This ALWAYS includes linting. If the project has a linter configured +(ESLint, Biome, ruff, etc.), you MUST run it and fix ALL violations in files you touched. +Do NOT leave lint warnings or errors for someone else to clean up. 6. Run REQUIRED situational tests based on changed surfaces (see `~/.config/mosaic/guides/E2E-DELIVERY.md` and `~/.config/mosaic/guides/QA-TESTING.md`). 7. If task is bug fix/security/auth/critical business logic, apply REQUIRED TDD discipline per `~/.config/mosaic/guides/QA-TESTING.md`. 8. If gates or required situational tests fail: Fix and retry. Do NOT report success with failures. 9. Commit: `git commit -m "fix({finding_id}): brief description"` 10. Before push, run queue guard: `~/.config/mosaic/tools/git/ci-queue-wait.sh --purpose push -B main` 11. Push: `git push origin {branch}` 12. Report result as JSON (see format below) + +## Git Scripts + +For issue/PR/milestone operations, use scripts (NOT raw tea/gh): + +- `~/.config/mosaic/tools/git/issue-view.sh -i {N}` +- `~/.config/mosaic/tools/git/pr-create.sh -t "Title" -b "Desc" -B main` +- `~/.config/mosaic/tools/git/ci-queue-wait.sh --purpose push|merge -B main` +- `~/.config/mosaic/tools/git/pr-merge.sh -n {PR_NUMBER} -m squash` +- `~/.config/mosaic/tools/git/pr-ci-wait.sh -n {PR_NUMBER}` +- `~/.config/mosaic/tools/git/issue-close.sh -i {N}` + +Standard git commands (pull, commit, push, checkout) are fine. + +## Result Format (MANDATORY) + +End your response with this JSON block: + +```json +{ + "task_id": "{id}", + "status": "success|failed", + "used": "5.2K", + "commit_sha": "abc123", + "notes": "Brief summary of what was done" +} +``` + +`status=success` means "code pushed and ready for orchestrator integration gates"; +it does NOT mean PR merged/CI green/issue closed. + +## Post-Coding Review + +After you complete and push your changes, the orchestrator will independently +review your code using Codex (or a fallback review agent). If the review finds +blockers or critical security issues, a follow-up remediation task will be +created. You do NOT need to run the review yourself — the orchestrator handles it. + +## Rules + +- DO NOT modify docs/TASKS.md +- DO NOT claim other tasks +- Complete this single task, report results, done + +```` + +--- + +## Context Threshold Protocol (Orchestrator Replacement) + +**Threshold:** 55-60% context usage + +**Why replacement, not compaction?** +- Compaction causes **protocol drift** — agent "remembers" gist but loses specifics +- Post-compaction agents may violate core rules (e.g., letting workers modify tasks.md) +- Fresh orchestrator has **100% protocol fidelity** +- All state lives in `docs/TASKS.md` — the orchestrator is **stateless and replaceable** + +**At threshold (55-60%):** + +1. Complete current task +2. Persist all state: + - Update docs/TASKS.md with all progress + - Update docs/tasks/orchestrator-learnings.json with variances + - Commit and push both files +3. Output **ORCHESTRATOR HANDOFF** message with ready-to-use takeover kickstart +4. **STOP COMPLETELY** — do not continue working + +**Handoff message format:** + +``` +--- +⚠️ ORCHESTRATOR HANDOFF REQUIRED + +Context: {X}% — Replacement recommended to prevent drift + +Progress: {completed}/{total} tasks ({percentage}%) +Current phase: Phase {N} ({phase_name}) + +State persisted: +- docs/TASKS.md ✓ +- docs/tasks/orchestrator-learnings.json ✓ + +## Takeover Kickstart + +Copy and paste this to spawn a fresh orchestrator: + +--- +## Continuation Mission + +Continue {mission_description} from existing state. + +## Setup +- Project: {project_path} +- State: docs/TASKS.md (already populated) +- Protocol: ~/.config/mosaic/guides/ORCHESTRATOR.md +- Quality gates: {quality_gates_command} + +## Resume Point +- Next task: {task_id} +- Phase: {current_phase} +- Progress: {completed}/{total} tasks ({percentage}%) + +## Instructions +1. Read ~/.config/mosaic/guides/ORCHESTRATOR.md for protocol +2. Read docs/TASKS.md to understand current state +3. Continue execution from task {task_id} +4. Follow Two-Phase Completion Protocol +5. You are the SOLE writer of docs/TASKS.md +--- + +STOP: Terminate this session and spawn fresh orchestrator with the kickstart above. +--- +``` + +**Rules:** +- Do NOT attempt to compact yourself — compaction causes drift +- Do NOT continue past 60% +- Do NOT claim you can "just continue" — protocol drift is real +- STOP means STOP — the user (Coordinator) will spawn your replacement +- Include ALL context needed for the replacement in the takeover kickstart + +--- + +## Two-Phase Completion Protocol + +Each major phase uses a two-phase approach to maximize completion while managing diminishing returns. + +### Bulk Phase (Target: 90%) + +- Focus on tractable errors +- Parallelize where possible +- When 90% reached, transition to Polish (do NOT declare success) + +### Polish Phase (Target: 100%) + +1. **Inventory:** List all remaining errors with file:line +2. **Categorize:** + | Category | Criteria | Action | + |----------|----------|--------| + | Quick-win | <5 min, straightforward | Fix immediately | + | Medium | 5-30 min, clear path | Fix in order | + | Hard | >30 min or uncertain | Attempt 15 min, then document | + | Architectural | Requires design change | Document and defer | + +3. **Work priority:** Quick-win → Medium → Hard +4. **Document deferrals** in `docs/reports/deferred/deferred-errors.md`: + ```markdown + ## {PREFIX}-XXX: [Error description] + - File: path/to/file.ts:123 + - Error: [exact error message] + - Category: Hard | Architectural | Framework Limitation + - Reason: [why this is non-trivial] + - Suggested approach: [how to fix in future] + - Risk: Low | Medium | High + ``` + +5. **Phase complete when:** + - All Quick-win/Medium fixed + - All Hard attempted (fixed or documented) + - Architectural items documented with justification + +### Phase Boundary Rule + +Do NOT proceed to the next major phase until the current phase reaches Polish completion: + +``` +✅ Phase 2 Bulk: 91% +✅ Phase 2 Polish: 118 errors triaged + - 40 medium → fixed + - 78 low → EACH documented with rationale +✅ Phase 2 Complete: Created docs/reports/deferred/deferred-errors.md +→ NOW proceed to Phase 3 + +❌ WRONG: Phase 2 at 91%, "low priority acceptable", starting Phase 3 +``` + +### Reporting + +When transitioning from Bulk to Polish: +``` +Phase X Bulk Complete: {N}% ({fixed}/{total}) +Entering Polish Phase: {remaining} errors to triage +``` + +When Polish Phase complete: +``` +Phase X Complete: {final_pct}% ({fixed}/{total}) +- Quick-wins: {n} fixed +- Medium: {n} fixed +- Hard: {n} fixed, {n} documented +- Framework limitations: {n} documented +``` + +--- + +## Learning & Retrospective + +Orchestrators capture learnings to improve future estimation accuracy. + +### Variance Thresholds + +| Variance | Action | +|----------|--------| +| 0-30% | Log only (acceptable) | +| 30-50% | Flag for review | +| 50-100% | Capture learning to `docs/tasks/orchestrator-learnings.json` | +| >100% | CRITICAL — review task classification, possible mismatch | + +### Task Type Classification + +Classify tasks by description keywords for pattern analysis: + +| Type | Keywords | Base Estimate | +|------|----------|---------------| +| STYLE_FIX | "formatting", "prettier", "lint" | 3-5K | +| BULK_CLEANUP | "unused", "warnings", "~N files" | file_count × 550 | +| GUARD_ADD | "add guard", "decorator", "validation" | 5-8K | +| SECURITY_FIX | "sanitize", "injection", "XSS" | 8-12K × 2.5 | +| AUTH_ADD | "authentication", "auth" | 15-25K | +| REFACTOR | "refactor", "replace", "migrate" | 10-15K | +| TEST_ADD | "add tests", "coverage" | 15-25K | + +### Capture Learning + +When |variance| > 50%, append to `docs/tasks/orchestrator-learnings.json`: + +```json +{ + "task_id": "UC-CLEAN-003", + "task_type": "BULK_CLEANUP", + "estimate_k": 30, + "actual_k": 112.8, + "variance_pct": 276, + "characteristics": { + "file_count": 200, + "keywords": ["object injection", "type guards"] + }, + "analysis": "Multi-file type guards severely underestimated", + "captured_at": "2026-02-05T19:45:00Z" +} +``` + +### Retrospective Triggers + +| Trigger | Action | +|---------|--------| +| Phase verification task | Analyze phase variance, summarize patterns | +| 60% compaction | Persist learnings buffer, include in summary | +| Milestone complete | Full retrospective, generate heuristic proposals | + +### Enhanced Compaction Summary + +Include learnings in compaction output: + +``` +Session Summary (Compacting at 60%): + +Completed: MS-SEC-001 (15K→0.3K, -98%), MS-SEC-002 (8K→12K, +50%) +Quality: All gates passing + +Learnings Captured: +- MS-SEC-001: -98% variance — AUTH_ADD may need SKIP_IF_EXISTS category +- MS-SEC-002: +50% variance — XSS sanitization more complex than expected + +Remaining: MS-SEC-004 (ready), MS-SEC-005 through MS-SEC-010 +Next: MS-SEC-004 +``` + +### Cross-Project Learnings + +Universal heuristics are maintained in `~/.config/mosaic/guides/ORCHESTRATOR-LEARNINGS.md`. +After completing a milestone, review variance patterns and propose updates to the universal guide. + +--- + +## Report Cleanup + +QA automation generates report files in `docs/reports/qa-automation/pending/`. These must be cleaned up to prevent accumulation. + +**Directory structure:** +``` +docs/reports/qa-automation/ +├── pending/ # Reports awaiting processing +└── escalated/ # Reports for failed tasks (manual review needed) +``` + +**Gitignore:** Add this to project `.gitignore`: +``` +# Orchestrator reports (generated by QA automation, cleaned up after processing) +docs/reports/qa-automation/ +``` + +**Cleanup timing:** +| Event | Action | +|-------|--------| +| Task success | Delete matching reports from `pending/` | +| Task failed | Move reports to `escalated/` for investigation | +| Phase verification | Clean up all `pending/` reports for that phase | +| Milestone complete | Complete release + tag workflow, then archive or delete `escalated/` directory | + +**Cleanup commands:** +```bash +# After successful task (finding ID pattern, e.g., SEC-API-1) +find docs/reports/qa-automation/pending/ -name "*relevant-file-pattern*" -delete + +# After phase verification - clean all pending +rm -rf docs/reports/qa-automation/pending/* + +# Move failed task reports to escalated +mv docs/reports/qa-automation/pending/*failing-file* docs/reports/qa-automation/escalated/ +``` + +--- + +## Error Handling + +**Quality gates fail:** +1. Worker should retry up to 2 times +2. If still failing, worker reports `failed` with error details +3. Orchestrator updates tasks.md: keep `in-progress`, add notes +4. Orchestrator may re-spawn with error context, or mark `failed` and continue +5. If failed task blocks others: Report deadlock, STOP + +**Worker reports blocker:** +1. Update tasks.md with blocker notes +2. Skip to next unblocked task if possible +3. If all remaining tasks blocked: Report blockers, STOP + +**PR/CI/Issue wrapper failure:** +1. Record task status as `blocked` in `docs/TASKS.md`. +2. Record the exact failed wrapper command (full command line) in task notes and user report. +3. STOP orchestration for that task; do not mark complete and do not silently fall back to raw provider commands. + +**Git push conflict:** +1. `git pull --rebase` +2. If auto-resolves: push again +3. If conflict on tasks.md: Report, STOP (human resolves) + +--- + +## Stopping Criteria + +**ONLY stop if:** +1. All tasks in docs/TASKS.md are `done` +2. Critical blocker preventing progress (document and alert) +3. Context usage >= 55% — output COMPACTION REQUIRED checkpoint and wait +4. Absolute context limit reached AND cannot compact further +5. PRD is current and reflects delivered requirements (`docs/PRD.md` or `docs/PRD.json`) +6. Required documentation checklist is complete for applicable changes +7. For milestone completion, release + git tag steps are complete +8. For source-code tasks with external provider, merged PR evidence exists +9. For source-code tasks with external provider, CI/pipeline is terminal green +10. For linked external issues, closure is complete (or internal TASKS ref closure if no provider) + +**DO NOT stop to ask "should I continue?"** — the answer is always YES. +**DO stop at 55-60%** — output the compaction checkpoint and wait for user to run `/compact`. + +--- + +## Merge-to-Main Candidate Protocol (Container Deployments) + +If deployment is in scope and container images are used, every merge to `main` MUST execute this protocol: + +1. Build and push immutable candidate image tags: + - `sha-` (always) + - `v{base-version}-rc.{build}` (for `main` merges) + - `testing` mutable pointer to the same digest +2. Resolve and record the image digest for each service. +3. Deploy by digest to testing environment (never deploy by mutable tag alone). +4. Run full situational testing against images pulled from the registry. +5. If tests pass, promote the SAME digest (no rebuild) to environment pointers (`staging`/`prod` as applicable). +6. If tests fail, rollback to last known-good digest and create remediation tasks immediately. + +Hard rules: +- `latest` MUST NOT be used as a deployment reference. +- Final semantic release tags (`vX.Y.Z`) are milestone-level only. +- Intermediate checkpoints use RC image tags (`vX.Y.Z-rc.N`) and digest promotion. + +--- + +## Milestone Completion Protocol (Release + Tag Required) + +When all tasks in `docs/TASKS.md` are `done` (or triaged as `deferred`), you MUST complete release/tag operations before declaring the milestone complete. + +### Required Completion Steps + +1. **Prepare release metadata**: + - `milestone-name` (human-readable) + - `milestone-version` (semantic version, e.g., `0.0.3`, `0.1.0`) + - `tag` = `v{milestone-version}` (e.g., `v0.0.3`) + +2. **Verify documentation gate**: + - Confirm required docs were updated per `~/.config/mosaic/guides/DOCUMENTATION.md`. + - Confirm checklist completion: `~/.config/mosaic/templates/docs/DOCUMENTATION-CHECKLIST.md`. + - If docs are incomplete, STOP and create remediation task(s) before release/tag. + +3. **Create and push annotated git tag**: + ```bash + git pull --rebase + git tag -a "v{milestone-version}" -m "Release v{milestone-version} - {milestone-name}" + git push origin "v{milestone-version}" + ``` + +4. **Create repository release** (provider-specific): + + Gitea: + ```bash + tea releases create \ + --tag "v{milestone-version}" \ + --title "v{milestone-version}" \ + --note "Milestone {milestone-name} completed." + ``` + + GitHub: + ```bash + gh release create "v{milestone-version}" \ + --title "v{milestone-version}" \ + --notes "Milestone {milestone-name} completed." + ``` + + GitLab: + ```bash + glab release create "v{milestone-version}" \ + --name "v{milestone-version}" \ + --notes "Milestone {milestone-name} completed." + ``` + + No external provider fallback: + - Create and push annotated tag as above. + - Create `docs/releases/v{milestone-version}.md` with release notes and include milestone completion summary. + +5. **Close milestone in provider**: + - Gitea/GitHub: + ```bash + ~/.config/mosaic/tools/git/milestone-close.sh -t "{milestone-name}" + ``` + - GitLab: close milestone via provider workflow (CLI or web UI). + If provider tooling is unavailable, record milestone closure status in `docs/TASKS.md` notes. + +6. **Archive sprint artifacts**: + ```bash + mkdir -p docs/tasks/ + mv docs/TASKS.md docs/tasks/{milestone-name}-tasks.md + mv docs/tasks/orchestrator-learnings.json docs/tasks/{milestone-name}-learnings.json + ``` + Example: `docs/tasks/M6-AgentOrchestration-Fixes-tasks.md` + +7. **Commit archive + release references**: + ```bash + git add docs/tasks/ docs/releases/ 2>/dev/null || true + git rm docs/TASKS.md docs/tasks/orchestrator-learnings.json 2>/dev/null || true + git commit -m "chore(orchestrator): Complete {milestone-name} milestone release + + - Tagged: v{milestone-version} + - Release published + - Artifacts archived to docs/tasks/" + git push + ``` + +8. **Run final retrospective** — review variance patterns and propose updates to estimation heuristics. + +### Deployment Protocol (When In Scope) + +If the milestone includes deployment, orchestrator MUST complete deployment before final completion status: + +1. Determine deployment target from PRD, project config, or environment: + - `Portainer` + - `Coolify` + - `Vercel` + - other configured SaaS provider +2. Trigger deployment using provider API/CLI/webhook. +3. Deployment method MUST be digest-first: + - Resolve digest from candidate image (`sha-*` or `vX.Y.Z-rc.N`), + - deploy that digest, + - promote tags (`testing`/`staging`/`prod`) only after validation. +4. Run post-deploy verification: + - health endpoint checks, + - critical smoke tests, + - release/version verification, + - digest verification (running digest equals promoted digest). +5. Default strategy is blue-green. Canary is allowed only if automated metrics, thresholds, and rollback triggers are configured. +6. If verification fails, execute rollback/redeploy-safe path and mark milestone `blocked` until stable. +7. Record deployment evidence in milestone release notes and `docs/TASKS.md` notes, including digest and promoted tags. +8. Ensure registry cleanup is scheduled/enforced (retain release tags + active digests, purge stale RC/sha tags). + +### Recovery + +If an orchestrator starts and `docs/TASKS.md` does not exist, check `docs/tasks/` for the most recent archive: + +```bash +ls -t docs/tasks/*-tasks.md 2>/dev/null | head -1 +``` + +If found, this may indicate another session archived the file. The orchestrator should: +1. Report what it found in `docs/tasks/` +2. Ask whether to resume from the archived file or bootstrap fresh +3. If resuming: copy the archive back to `docs/TASKS.md` and continue + +### Retention Policy + +Keep all archived sprints indefinitely. They are small text files and valuable for: +- Post-mortem analysis +- Estimation variance calibration across milestones +- Understanding what was deferred and why +- Onboarding new orchestrators to project history + +--- + +## Kickstart Message Format + +The kickstart should be **minimal** — the orchestrator figures out the rest: + +```markdown +## Mission +Remediate findings from the codebase review. + +## Setup +- Project: /path/to/project +- Review: docs/reports/{report-name}/ +- Quality gates: {command} +- Milestone: {milestone-name} (for issue creation) +- Task prefix: {PREFIX} (e.g., MS, UC) + +## Protocol +Read ~/.config/mosaic/guides/ORCHESTRATOR.md for full instructions. + +## Start +Bootstrap from the review report, then execute until complete. +``` + +**The orchestrator will:** +1. Read this guide +2. Parse the review reports +3. Determine phases, estimates, dependencies +4. Create issues and tasks.md +5. Execute until done or blocked + +--- + +## Quick Reference + +| Phase | Action | +|-------|--------| +| Bootstrap | Parse reports → Categorize → Estimate → Create issues → Create tasks.md | +| Execute | Loop: claim → spawn worker → update → commit | +| Compact | At 60%: summarize, clear history, continue | +| Stop | Queue empty, blocker, or context limit | + +**Orchestrator owns tasks.md. Workers execute and report. Single writer eliminates conflicts.** +```` diff --git a/packages/mosaic/framework/guides/PRD.md b/packages/mosaic/framework/guides/PRD.md new file mode 100644 index 0000000..bf3540b --- /dev/null +++ b/packages/mosaic/framework/guides/PRD.md @@ -0,0 +1,63 @@ +# PRD Requirement Guide (MANDATORY) + +This guide defines how requirements are captured before coding. + +## Hard Rules + +1. Before coding begins, `docs/PRD.md` or `docs/PRD.json` MUST exist. +2. The PRD is the authoritative requirements source for implementation and testing. +3. The main agent MUST prepare or update the PRD using user input and available project context before implementation starts. +4. The agent MUST NOT invent requirements silently. +5. In steered autonomy mode, best-guess decisions are REQUIRED when needed; each guessed decision MUST be marked with `ASSUMPTION:` and rationale. + +## PRD Format + +Allowed canonical formats: + +1. `docs/PRD.md` +2. `docs/PRD.json` + +Either format is valid. Both may exist if one is a transformed representation of the other. +For markdown PRDs, start from `~/.config/mosaic/templates/docs/PRD.md.template`. + +## Best-Guess Mode + +Steered autonomy is the default operating mode. + +1. Agent SHOULD fill missing decisions in the PRD without waiting for routine confirmation. +2. Agent MUST mark each guessed decision with `ASSUMPTION:` and rationale. +3. If user explicitly requests strict-confirmation mode, the agent MUST ask before unresolved decisions are finalized. +4. For high-impact security/compliance/release uncertainty, escalate only if the decision cannot be safely constrained with rollback-ready defaults. + +## Minimum PRD Content + +Every PRD MUST include: + +1. Problem statement and objective +2. In-scope and out-of-scope +3. User/stakeholder requirements +4. Functional requirements +5. Non-functional requirements (security, performance, reliability, observability) +6. Acceptance criteria +7. Constraints and dependencies +8. Risks and open questions +9. Testing and verification expectations +10. Delivery/milestone intent + +## Pre-Coding Gate + +Coding MUST NOT begin until: + +1. PRD file exists (`docs/PRD.md` or `docs/PRD.json`) +2. PRD has required sections +3. Unresolved decisions are captured as explicit `ASSUMPTION:` entries with rationale and planned validation + +## Change Control + +When requirements materially change: + +1. Update PRD first. +2. Then update implementation plan/tasks. +3. Then implement code changes. + +Implementation that diverges from PRD without PRD updates is a blocker. diff --git a/packages/mosaic/framework/guides/QA-TESTING.md b/packages/mosaic/framework/guides/QA-TESTING.md new file mode 100644 index 0000000..c5d3500 --- /dev/null +++ b/packages/mosaic/framework/guides/QA-TESTING.md @@ -0,0 +1,125 @@ +# QA & Testing Guide + +## Before Starting + +1. Check assigned issue: `~/.config/mosaic/tools/git/issue-list.sh -a @me` +2. Create scratchpad: `docs/scratchpads/{issue-number}-{short-name}.md` +3. Review `docs/PRD.md` or `docs/PRD.json` as the requirements source. +4. Review acceptance criteria and affected change surfaces. + +## Testing Policy (Hard Rules) + +1. Situational testing is the PRIMARY validation gate. +2. Baseline testing is REQUIRED for all software changes. +3. TDD is risk-based and REQUIRED only for defined high-risk change types. +4. Tests MUST validate requirements and behavior, not only internal implementation details. + +## Priority Order + +1. Situational tests: prove requirements and real behavior on changed surfaces. +2. Baseline tests: lint/type/unit/integration safety checks. +3. TDD discipline: applied where risk justifies test-first workflow. + +## Risk-Based TDD Requirement + +| Change Type | TDD Requirement | Required Action | +| ---------------------------------------------- | --------------- | ---------------------------------------------------------------------- | +| Bug fix | REQUIRED | Write a failing reproducer test first, then fix. | +| Security/auth/permission logic | REQUIRED | Write failing security/permission-path test first. | +| Critical business logic or data mutation rules | REQUIRED | Write failing rule/invariant test first. | +| API behavior regression | REQUIRED | Write failing contract/behavior test first. | +| Low-risk UI copy/style/layout | OPTIONAL | Add verification tests as appropriate; TDD recommended, not mandatory. | +| Mechanical refactor with unchanged behavior | OPTIONAL | Ensure regression/smoke coverage and situational evidence. | + +If TDD is not required and skipped, record rationale in scratchpad. +If TDD is required and skipped, task is NOT complete. + +## Baseline Test Requirements + +For all software changes, run baseline checks applicable to the repo: + +1. lint/static checks +2. type checks +3. unit tests for changed logic +4. integration tests for changed boundaries + +## Situational Testing Matrix (Primary Gate) + +| Change Surface | Required Situational Tests | +| ---------------------------- | ----------------------------------------------------------------------------- | +| Authentication/authorization | auth failure-path tests, permission boundary tests, token/session validation | +| Database schema/migrations | migration up/down validation, rollback safety, data integrity checks | +| API contract changes | backward compatibility checks, consumer-impact tests, contract tests | +| Frontend/UI workflow changes | end-to-end flow tests, accessibility sanity checks, state transition checks | +| CI/CD or deployment changes | pipeline execution validation, artifact integrity checks, rollback path check | +| Security-sensitive logic | abuse-case tests, input validation fuzzing/sanitization checks | +| Performance-critical path | baseline comparison, regression threshold checks | + +## Coverage Requirements + +### Minimum Standards + +- Overall Coverage: 85% minimum +- Critical Paths: 95% minimum (auth, payments, data mutations) +- New Code: 90% minimum + +Coverage is necessary but NOT sufficient. Passing coverage does not replace situational verification. + +## Requirements-to-Evidence Mapping (Mandatory) + +Before completion, map each acceptance criterion to concrete evidence. +Acceptance criteria MUST come from the active PRD. + +Template: + +```markdown +| Acceptance Criterion | Verification Method | Evidence | +| -------------------- | ------------------------------------------------------ | ---------------- | +| AC-1: ... | Situational test / baseline test / manual verification | command + result | +| AC-2: ... | ... | ... | +``` + +## Browser Automation (Hard Rule) + +All browser automation (Playwright, Cypress, Puppeteer) MUST run in **headless mode**. +Launching a visible browser collides with the user's display and active session. + +- Playwright: use `headless: true` in config or `--headed` must NOT be passed +- Cypress: use `cypress run` (headless by default), never `cypress open` +- Puppeteer: use `headless: true` (default) + +If a project's `playwright.config.ts` does not explicitly set `headless: true`, add it before running tests. + +## Test Quality Rules + +1. Test behavior and outcomes, not private implementation details. +2. Include failure-path and edge-case assertions for changed behavior. +3. Keep tests deterministic; no new flaky tests. +4. Keep tests isolated; no dependency on execution order. + +## Anti-Gaming Rules + +1. Do NOT stop at "tests pass" if acceptance criteria are not verified. +2. Do NOT write narrow tests that only satisfy assertions while missing real workflow behavior. +3. Do NOT claim completion without situational evidence for impacted surfaces. + +## Reporting + +QA report MUST include: + +1. baseline tests run and outcomes +2. situational tests run and outcomes +3. TDD usage decision (required/applied or optional/skipped with rationale) +4. acceptance-criteria-to-evidence mapping +5. coverage results +6. residual risk notes + +## Before Completing + +1. Baseline tests pass. +2. Required situational tests pass. +3. TDD obligations met for required change types. +4. Acceptance criteria mapped to evidence. +5. No flaky tests introduced. +6. CI pipeline passes (if available). +7. Scratchpad updated with results. diff --git a/packages/mosaic/framework/guides/TYPESCRIPT.md b/packages/mosaic/framework/guides/TYPESCRIPT.md new file mode 100644 index 0000000..ef6a2b6 --- /dev/null +++ b/packages/mosaic/framework/guides/TYPESCRIPT.md @@ -0,0 +1,440 @@ +# TypeScript Style Guide + +**Authority**: This guide is MANDATORY for all TypeScript code. No exceptions without explicit approval. + +Based on Google TypeScript Style Guide with stricter enforcement. + +--- + +## Core Principles + +1. **Explicit over implicit** — Always declare types, never rely on inference for public APIs +2. **Specific over generic** — Use the narrowest type that works +3. **Safe over convenient** — Type safety is not negotiable +4. **Contract-first boundaries** — Cross-module and API payloads MUST use dedicated DTO files + +--- + +## DTO Contract (MANDATORY) + +DTO files are REQUIRED for TypeScript module boundaries to preserve shared context and consistency. + +Hard requirements: + +1. Input and output payloads crossing module boundaries MUST be defined in `*.dto.ts` files. +2. Controller/service boundary payloads MUST use DTO types; inline object literal types are NOT allowed. +3. Public API request/response contracts MUST use DTO files and remain stable across modules. +4. Shared DTOs used by multiple modules MUST live in a shared location (for example `src/shared/dto/` or `packages/shared/dto/`). +5. ORM/entity models MUST NOT be exposed directly across module boundaries; map them to DTOs. +6. DTO changes MUST be reflected in tests and documentation when contracts change. + +```typescript +// ❌ WRONG: inline payload contract at boundary +export function createUser(payload: { email: string; role: string }): Promise {} + +// ✅ CORRECT: dedicated DTO file contract +// user-create.dto.ts +export interface UserCreateDto { + email: string; + role: UserRole; +} + +// user-response.dto.ts +export interface UserResponseDto { + id: string; + email: string; + role: UserRole; +} + +// service.ts +export function createUser(payload: UserCreateDto): Promise {} +``` + +--- + +## Forbidden Patterns (NEVER USE) + +### `any` Type — FORBIDDEN + +```typescript +// ❌ NEVER +function process(data: any) {} +const result: any = fetchData(); +Record; + +// ✅ ALWAYS define explicit types +interface UserData { + id: string; + name: string; + email: string; +} +function process(data: UserData) {} +``` + +### `unknown` as Lazy Typing — FORBIDDEN + +`unknown` is only acceptable in these specific cases: + +1. Error catch blocks (then immediately narrow) +2. JSON.parse results (then validate with Zod/schema) +3. External API responses before validation + +```typescript +// ❌ NEVER - using unknown to avoid typing +function getData(): unknown {} +const config: Record = {}; + +// ✅ ACCEPTABLE - error handling with immediate narrowing +try { + riskyOperation(); +} catch (error: unknown) { + if (error instanceof Error) { + logger.error(error.message); + } else { + logger.error('Unknown error', { error: String(error) }); + } +} + +// ✅ ACCEPTABLE - external data with validation +const raw: unknown = JSON.parse(response); +const validated = UserSchema.parse(raw); // Zod validation +``` + +### Implicit `any` — FORBIDDEN + +```typescript +// ❌ NEVER - implicit any from missing types +function process(data) {} // Parameter has implicit any +const handler = (e) => {}; // Parameter has implicit any + +// ✅ ALWAYS - explicit types +function process(data: RequestPayload): ProcessedResult {} +const handler = (e: React.MouseEvent): void => {}; +``` + +### Type Assertions to Bypass Safety — FORBIDDEN + +```typescript +// ❌ NEVER - lying to the compiler +const user = data as User; +const element = document.getElementById('app') as HTMLDivElement; + +// ✅ USE - type guards and narrowing +function isUser(data: unknown): data is User { + return typeof data === 'object' && data !== null && 'id' in data; +} +if (isUser(data)) { + console.log(data.id); // Safe +} + +// ✅ USE - null checks +const element = document.getElementById('app'); +if (element instanceof HTMLDivElement) { + element.style.display = 'none'; // Safe +} +``` + +### Non-null Assertion (`!`) — FORBIDDEN (except tests) + +```typescript +// ❌ NEVER in production code +const name = user!.name; +const element = document.getElementById('app')!; + +// ✅ USE - proper null handling +const name = user?.name ?? 'Anonymous'; +const element = document.getElementById('app'); +if (element) { + // Safe to use element +} +``` + +--- + +## Required Patterns + +### Explicit Return Types — REQUIRED for all public functions + +```typescript +// ❌ WRONG - missing return type +export function calculateTotal(items: Item[]) { + return items.reduce((sum, item) => sum + item.price, 0); +} + +// ✅ CORRECT - explicit return type +export function calculateTotal(items: Item[]): number { + return items.reduce((sum, item) => sum + item.price, 0); +} +``` + +### Explicit Parameter Types — REQUIRED always + +```typescript +// ❌ WRONG +const multiply = (a, b) => a * b; +users.map((user) => user.name); // If user type isn't inferred + +// ✅ CORRECT +const multiply = (a: number, b: number): number => a * b; +users.map((user: User): string => user.name); +``` + +### Interface Over Type Alias — PREFERRED for objects + +```typescript +// ✅ PREFERRED - interface (extendable, better error messages) +interface User { + id: string; + name: string; + email: string; +} + +// ✅ ACCEPTABLE - type alias for unions, intersections, primitives +type Status = 'active' | 'inactive' | 'pending'; +type ID = string | number; +``` + +### Const Assertions for Literals — REQUIRED + +```typescript +// ❌ WRONG - loses literal types +const config = { + endpoint: '/api/users', + method: 'GET', +}; +// config.method is string, not 'GET' + +// ✅ CORRECT - preserves literal types +const config = { + endpoint: '/api/users', + method: 'GET', +} as const; +// config.method is 'GET' +``` + +### Discriminated Unions — REQUIRED for variants + +```typescript +// ❌ WRONG - optional properties for variants +interface ApiResponse { + success: boolean; + data?: User; + error?: string; +} + +// ✅ CORRECT - discriminated union +interface SuccessResponse { + success: true; + data: User; +} +interface ErrorResponse { + success: false; + error: string; +} +type ApiResponse = SuccessResponse | ErrorResponse; +``` + +--- + +## Generic Constraints + +### Meaningful Constraints — REQUIRED + +```typescript +// ❌ WRONG - unconstrained generic +function merge(a: T, b: T): T {} + +// ✅ CORRECT - constrained generic +function merge(a: T, b: Partial): T {} +``` + +### Default Generic Parameters — USE SPECIFIC TYPES + +```typescript +// ❌ WRONG +interface Repository {} + +// ✅ CORRECT - no default if type should be explicit +interface Repository {} + +// ✅ ACCEPTABLE - meaningful default +interface Cache {} +``` + +--- + +## React/JSX Specific + +### Event Handlers — EXPLICIT TYPES REQUIRED + +```typescript +// ❌ WRONG +const handleClick = (e) => {}; +const handleChange = (e) => {}; + +// ✅ CORRECT +const handleClick = (e: React.MouseEvent): void => {}; +const handleChange = (e: React.ChangeEvent): void => {}; +const handleSubmit = (e: React.FormEvent): void => {}; +``` + +### Component Props — INTERFACE REQUIRED + +```typescript +// ❌ WRONG - inline types +function Button({ label, onClick }: { label: string; onClick: () => void }) { } + +// ✅ CORRECT - named interface +interface ButtonProps { + label: string; + onClick: () => void; + disabled?: boolean; +} + +function Button({ label, onClick, disabled = false }: ButtonProps): JSX.Element { + return ; +} +``` + +### Children Prop — USE React.ReactNode + +```typescript +interface LayoutProps { + children: React.ReactNode; + sidebar?: React.ReactNode; +} +``` + +--- + +## API Response Typing + +### Define Explicit Response Types + +```typescript +// ❌ WRONG +const response = await fetch('/api/users'); +const data = await response.json(); // data is any + +// ✅ CORRECT +interface UsersResponse { + users: User[]; + pagination: PaginationInfo; +} + +const response = await fetch('/api/users'); +const data: UsersResponse = await response.json(); + +// ✅ BEST - with runtime validation +const response = await fetch('/api/users'); +const raw = await response.json(); +const data = UsersResponseSchema.parse(raw); // Zod validates at runtime +``` + +--- + +## Error Handling + +### Typed Error Classes — REQUIRED for domain errors + +```typescript +class ValidationError extends Error { + constructor( + message: string, + public readonly field: string, + public readonly code: string, + ) { + super(message); + this.name = 'ValidationError'; + } +} + +class NotFoundError extends Error { + constructor( + public readonly resource: string, + public readonly id: string, + ) { + super(`${resource} with id ${id} not found`); + this.name = 'NotFoundError'; + } +} +``` + +### Error Narrowing — REQUIRED + +```typescript +try { + await saveUser(user); +} catch (error: unknown) { + if (error instanceof ValidationError) { + return { error: error.message, field: error.field }; + } + if (error instanceof NotFoundError) { + return { error: 'Not found', resource: error.resource }; + } + if (error instanceof Error) { + logger.error('Unexpected error', { message: error.message, stack: error.stack }); + return { error: 'Internal error' }; + } + logger.error('Unknown error type', { error: String(error) }); + return { error: 'Internal error' }; +} +``` + +--- + +## ESLint Rules — ENFORCE THESE + +```javascript +{ + "@typescript-eslint/no-explicit-any": "error", + "@typescript-eslint/explicit-function-return-type": ["error", { + "allowExpressions": true, + "allowTypedFunctionExpressions": true + }], + "@typescript-eslint/explicit-module-boundary-types": "error", + "@typescript-eslint/no-inferrable-types": "off", // Allow explicit primitives + "@typescript-eslint/no-non-null-assertion": "error", + "@typescript-eslint/strict-boolean-expressions": "error", + "@typescript-eslint/no-unsafe-assignment": "error", + "@typescript-eslint/no-unsafe-member-access": "error", + "@typescript-eslint/no-unsafe-call": "error", + "@typescript-eslint/no-unsafe-return": "error" +} +``` + +--- + +## TSConfig Strict Mode — REQUIRED + +```json +{ + "compilerOptions": { + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "strictBindCallApply": true, + "strictPropertyInitialization": true, + "noImplicitThis": true, + "useUnknownInCatchVariables": true, + "noUncheckedIndexedAccess": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noImplicitOverride": true + } +} +``` + +--- + +## Summary: The Type Safety Hierarchy + +From best to worst: + +1. **Explicit specific type** (interface/type) — REQUIRED +2. **Generic with constraints** — ACCEPTABLE +3. **`unknown` with immediate validation** — ONLY for external data +4. **`any`** — FORBIDDEN + +**When in doubt, define an interface.** diff --git a/packages/mosaic/framework/guides/VAULT-SECRETS.md b/packages/mosaic/framework/guides/VAULT-SECRETS.md new file mode 100644 index 0000000..b1dda96 --- /dev/null +++ b/packages/mosaic/framework/guides/VAULT-SECRETS.md @@ -0,0 +1,205 @@ +# Vault Secrets Management Guide + +This guide applies when the project uses HashiCorp Vault for secrets management. + +## Before Starting + +1. Verify Vault access: `vault status` +2. Authenticate: `vault login` (method depends on environment) +3. Check your permissions for the required paths + +## Canonical Structure + +**ALL Vault secrets MUST follow this structure:** + +``` +{mount}/{service}/{component}/{secret-name} +``` + +### Components + +- **mount**: Environment-specific mount point +- **service**: The service or application name +- **component**: Logical grouping (database, api, oauth, etc.) +- **secret-name**: Specific secret identifier + +## Environment Mounts + +| Mount | Environment | Usage | +| ----------------- | ----------- | ---------------------- | +| `secret-dev/` | Development | Local dev, CI | +| `secret-staging/` | Staging | Pre-production testing | +| `secret-prod/` | Production | Live systems | + +## Examples + +```bash +# Database credentials +secret-prod/postgres/database/app +secret-prod/mysql/database/readonly +secret-staging/redis/auth/default + +# API tokens +secret-prod/authentik/admin/token +secret-prod/stripe/api/live-key +secret-dev/sendgrid/api/test-key + +# JWT/Authentication +secret-prod/backend-api/jwt/signing-key +secret-prod/auth-service/session/secret + +# OAuth providers +secret-prod/backend-api/oauth/google +secret-prod/backend-api/oauth/github + +# Internal services +secret-prod/loki/read-auth/admin +secret-prod/grafana/admin/password +``` + +## Standard Field Names + +Use consistent field names within secrets: + +| Purpose | Fields | +| ----------- | ---------------------------- | +| Credentials | `username`, `password` | +| Tokens | `token` | +| OAuth | `client_id`, `client_secret` | +| Connection | `url`, `host`, `port` | +| Keys | `public_key`, `private_key` | + +### Example Secret Structure + +```json +// secret-prod/postgres/database/app +{ + "username": "app_user", + "password": "secure-password-here", + "host": "db.example.com", + "port": "5432", + "database": "myapp" +} +``` + +## Rules + +1. **DO NOT GUESS** secret paths - Always verify the path exists +2. **Use helper scripts** in `scripts/vault/` when available +3. **All lowercase, hyphenated** (kebab-case) for all path segments +4. **Standard field names** - Use the conventions above +5. **No sensitive data in path names** - Path itself should not reveal secrets +6. **Environment separation** - Never reference prod secrets from dev + +## Deprecated Paths (DO NOT USE) + +These legacy patterns are deprecated and should be migrated: + +| Deprecated | Migrate To | +| ------------------------- | ------------------------------------------- | +| `secret/infrastructure/*` | `secret-{env}/{service}/...` | +| `secret/oauth/*` | `secret-{env}/{service}/oauth/{provider}` | +| `secret/database/*` | `secret-{env}/{service}/database/{user}` | +| `secret/credentials/*` | `secret-{env}/{service}/{component}/{name}` | + +## Reading Secrets + +### CLI + +```bash +# Read a secret +vault kv get secret-prod/postgres/database/app + +# Get specific field +vault kv get -field=password secret-prod/postgres/database/app + +# JSON output +vault kv get -format=json secret-prod/postgres/database/app +``` + +### Application Code + +**Python (hvac):** + +```python +import hvac + +client = hvac.Client(url='https://vault.example.com') +secret = client.secrets.kv.v2.read_secret_version( + path='postgres/database/app', + mount_point='secret-prod' +) +password = secret['data']['data']['password'] +``` + +**Node.js (node-vault):** + +```javascript +const vault = require('node-vault')({ endpoint: 'https://vault.example.com' }); +const secret = await vault.read('secret-prod/data/postgres/database/app'); +const password = secret.data.data.password; +``` + +**Go:** + +```go +secret, err := client.Logical().Read("secret-prod/data/postgres/database/app") +password := secret.Data["data"].(map[string]interface{})["password"].(string) +``` + +## Writing Secrets + +Only authorized personnel should write secrets. If you need a new secret: + +1. Request through proper channels (ticket, PR to IaC repo) +2. Follow the canonical structure +3. Document the secret's purpose +4. Set appropriate access policies + +```bash +# Example (requires write permissions) +vault kv put secret-dev/myapp/database/app \ + username="dev_user" \ + password="dev-password" \ + host="localhost" \ + port="5432" +``` + +## Troubleshooting + +### Permission Denied + +``` +Error: permission denied +``` + +- Verify your token has read access to the path +- Check if you're using the correct mount point +- Confirm the secret path exists + +### Secret Not Found + +``` +Error: no value found at secret-prod/data/service/component/name +``` + +- Verify the exact path (use `vault kv list` to explore) +- Check for typos in service/component names +- Confirm you're using the correct environment mount + +### Token Expired + +``` +Error: token expired +``` + +- Re-authenticate: `vault login` +- Check token TTL: `vault token lookup` + +## Security Best Practices + +1. **Least privilege** - Request only the permissions you need +2. **Short-lived tokens** - Use tokens with appropriate TTLs +3. **Audit logging** - All access is logged; act accordingly +4. **No local copies** - Don't store secrets in files or env vars long-term +5. **Rotate on compromise** - Immediately rotate any exposed secrets diff --git a/packages/mosaic/framework/install.ps1 b/packages/mosaic/framework/install.ps1 new file mode 100644 index 0000000..063e1a2 --- /dev/null +++ b/packages/mosaic/framework/install.ps1 @@ -0,0 +1,231 @@ +# Mosaic Bootstrap — Windows Installer +# PowerShell equivalent of install.sh +# +# Usage: +# powershell -ExecutionPolicy Bypass -File install.ps1 +# +$ErrorActionPreference = "Stop" + +$SourceDir = $PSScriptRoot +$TargetDir = if ($env:MOSAIC_HOME) { $env:MOSAIC_HOME } else { Join-Path $env:USERPROFILE ".config\mosaic" } +$InstallMode = if ($env:MOSAIC_INSTALL_MODE) { $env:MOSAIC_INSTALL_MODE.ToLowerInvariant() } else { "prompt" } # prompt|keep|overwrite +$PreservePaths = @("SOUL.md", "memory") + +function Write-Ok { param([string]$Msg) Write-Host " ✓ " -ForegroundColor Green -NoNewline; Write-Host $Msg } +function Write-Warn { param([string]$Msg) Write-Host " ⚠ " -ForegroundColor Yellow -NoNewline; Write-Host $Msg } +function Write-Fail { param([string]$Msg) Write-Host " ✗ " -ForegroundColor Red -NoNewline; Write-Host $Msg } +function Write-Step { param([string]$Msg) Write-Host ""; Write-Host $Msg -ForegroundColor White -BackgroundColor DarkGray } + +function Test-ExistingInstall { + if (-not (Test-Path $TargetDir)) { return $false } + return (Test-Path (Join-Path $TargetDir "bin\mosaic")) -or (Test-Path (Join-Path $TargetDir "AGENTS.md")) -or (Test-Path (Join-Path $TargetDir "SOUL.md")) +} + +function Select-InstallMode { + switch ($InstallMode) { + "prompt" { } + "keep" { return } + "overwrite" { return } + default { + Write-Fail "Invalid MOSAIC_INSTALL_MODE '$InstallMode'. Use: prompt, keep, overwrite." + exit 1 + } + } + + if (-not (Test-ExistingInstall)) { + $script:InstallMode = "overwrite" + return + } + + if (-not [Environment]::UserInteractive) { + Write-Warn "Existing install detected without interactive input; defaulting to keep local files." + $script:InstallMode = "keep" + return + } + + Write-Host "" + Write-Host "Existing Mosaic install detected at: $TargetDir" + Write-Host "Choose reinstall mode:" + Write-Host " 1) keep Keep local files (SOUL.md, memory/) while updating framework" + Write-Host " 2) overwrite Replace everything in $TargetDir" + Write-Host " 3) cancel Abort install" + $selection = Read-Host "Selection [1/2/3] (default: 1)" + + $normalizedSelection = if ($null -eq $selection) { "" } else { $selection.ToLowerInvariant() } + switch ($normalizedSelection) { + "" { $script:InstallMode = "keep" } + "1" { $script:InstallMode = "keep" } + "k" { $script:InstallMode = "keep" } + "keep" { $script:InstallMode = "keep" } + "2" { $script:InstallMode = "overwrite" } + "o" { $script:InstallMode = "overwrite" } + "overwrite" { $script:InstallMode = "overwrite" } + "3" { Write-Fail "Install cancelled."; exit 1 } + "c" { Write-Fail "Install cancelled."; exit 1 } + "cancel" { Write-Fail "Install cancelled."; exit 1 } + default { + Write-Warn "Unrecognized selection '$selection'; defaulting to keep." + $script:InstallMode = "keep" + } + } +} + +# ── Install framework ──────────────────────────────────────── +Write-Step " Installing Mosaic framework " + +if (-not (Test-Path $TargetDir)) { New-Item -ItemType Directory -Path $TargetDir -Force | Out-Null } +Select-InstallMode + +if ($InstallMode -eq "keep") { + Write-Ok "Install mode: keep local SOUL.md/memory while updating framework" +} +else { + Write-Ok "Install mode: overwrite existing files" +} + +$preserveTmp = $null +if ($InstallMode -eq "keep") { + $preserveTmp = Join-Path ([System.IO.Path]::GetTempPath()) ("mosaic-preserve-" + [Guid]::NewGuid().ToString("N")) + New-Item -ItemType Directory -Path $preserveTmp -Force | Out-Null + foreach ($relPath in $PreservePaths) { + $src = Join-Path $TargetDir $relPath + if (Test-Path $src) { + $dstParent = Join-Path $preserveTmp (Split-Path $relPath -Parent) + if (-not [string]::IsNullOrEmpty($dstParent) -and -not (Test-Path $dstParent)) { + New-Item -ItemType Directory -Path $dstParent -Force | Out-Null + } + Copy-Item $src (Join-Path $preserveTmp $relPath) -Recurse -Force + } + } +} + +Get-ChildItem $TargetDir -Exclude ".git" | Remove-Item -Recurse -Force +Get-ChildItem $SourceDir -Exclude ".git" | ForEach-Object { + $dest = Join-Path $TargetDir $_.Name + if ($_.PSIsContainer) { + Copy-Item $_.FullName $dest -Recurse -Force + } + else { + Copy-Item $_.FullName $dest -Force + } +} + +if ($InstallMode -eq "keep" -and $null -ne $preserveTmp) { + foreach ($relPath in $PreservePaths) { + $src = Join-Path $preserveTmp $relPath + if (Test-Path $src) { + $dst = Join-Path $TargetDir $relPath + if (Test-Path $dst) { + Remove-Item $dst -Recurse -Force + } + $dstParent = Split-Path $dst -Parent + if (-not (Test-Path $dstParent)) { + New-Item -ItemType Directory -Path $dstParent -Force | Out-Null + } + Copy-Item $src $dst -Recurse -Force + } + } + Remove-Item -Path $preserveTmp -Recurse -Force -ErrorAction SilentlyContinue +} + +Write-Ok "Framework installed to $TargetDir" + +# ── Post-install tasks ─────────────────────────────────────── +Write-Step " Post-install tasks " + +$binDir = Join-Path $TargetDir "bin" + +try { + & "$binDir\mosaic-link-runtime-assets.ps1" *>$null + Write-Ok "Runtime assets linked" +} +catch { + Write-Warn "Runtime asset linking failed (non-fatal)" +} + +try { + & "$binDir\mosaic-ensure-sequential-thinking.ps1" *>$null + Write-Ok "sequential-thinking MCP configured" +} +catch { + if ($env:MOSAIC_ALLOW_MISSING_SEQUENTIAL_THINKING -eq "1") { + Write-Warn "sequential-thinking MCP setup failed but bypassed (MOSAIC_ALLOW_MISSING_SEQUENTIAL_THINKING=1)" + } + else { + Write-Fail "sequential-thinking MCP setup failed (hard requirement)." + Write-Fail "Set MOSAIC_ALLOW_MISSING_SEQUENTIAL_THINKING=1 only for temporary bypass scenarios." + exit 1 + } +} + +if ($env:MOSAIC_SKIP_SKILLS_SYNC -eq "1") { + Write-Ok "Skills sync skipped (MOSAIC_SKIP_SKILLS_SYNC=1)" +} +else { + try { + & "$binDir\mosaic-sync-skills.ps1" *>$null + Write-Ok "Skills synced" + } + catch { + Write-Warn "Skills sync failed (non-fatal)" + } +} + +try { + & "$binDir\mosaic-migrate-local-skills.ps1" -Apply *>$null + Write-Ok "Local skills migrated" +} +catch { + Write-Warn "Local skill migration failed (non-fatal)" +} + +try { + & "$binDir\mosaic-doctor.ps1" *>$null + Write-Ok "Health audit passed" +} +catch { + Write-Warn "Health audit reported issues — run 'mosaic doctor' for details" +} + +# ── PATH configuration ─────────────────────────────────────── +Write-Step " PATH configuration " + +$mosaicBin = Join-Path $TargetDir "bin" +$userPath = [Environment]::GetEnvironmentVariable("Path", "User") +$pathChanged = $false + +if ($userPath -and $userPath.Split(";") -contains $mosaicBin) { + Write-Ok "Already in User PATH" +} +else { + $newPath = if ($userPath) { "$mosaicBin;$userPath" } else { $mosaicBin } + [Environment]::SetEnvironmentVariable("Path", $newPath, "User") + $env:Path = "$mosaicBin;$env:Path" + Write-Ok "Added to User PATH" + $pathChanged = $true +} + +# ── Summary ────────────────────────────────────────────────── +Write-Host "" +Write-Host " Mosaic installed successfully." -ForegroundColor Green +Write-Host "" + +$nextSteps = @() + +if ($pathChanged) { + $nextSteps += "Open a new terminal (or log out and back in) to activate PATH." +} + +$soulPath = Join-Path $TargetDir "SOUL.md" +if (-not (Test-Path $soulPath)) { + $nextSteps += "Run 'mosaic init' to set up your agent identity (SOUL.md)." +} + +if ($nextSteps.Count -gt 0) { + Write-Host " Next steps:" -ForegroundColor White + for ($i = 0; $i -lt $nextSteps.Count; $i++) { + Write-Host " $($i + 1). " -NoNewline + Write-Host $nextSteps[$i] -ForegroundColor Cyan + } + Write-Host "" +} diff --git a/packages/mosaic/framework/memory/.gitkeep b/packages/mosaic/framework/memory/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/packages/mosaic/framework/profiles/README.md b/packages/mosaic/framework/profiles/README.md new file mode 100644 index 0000000..632e0c9 --- /dev/null +++ b/packages/mosaic/framework/profiles/README.md @@ -0,0 +1,22 @@ +# Mosaic Profiles + +Profiles are runtime-neutral context packs that can be consumed by any agent runtime. + +## Layout + +- `domains/`: regulated-domain and security context (HIPAA, fintech, crypto, etc.) +- `tech-stacks/`: stack-specific conventions and quality checks +- `workflows/`: reusable execution workflows + +## Runtime Split + +- Runtime-neutral content belongs here under `~/.config/mosaic/profiles`. +- Runtime-specific settings belong under `~/.config/mosaic/runtime//...`. + +Current runtime overlay example: + +- `~/.config/mosaic/runtime/claude/settings-overlays/jarvis-loop.json` + +## Claude Compatibility + +`mosaic-link-runtime-assets` prunes legacy preset symlink trees from `~/.claude` so Mosaic remains canonical and Claude uses runtime overlays that reference Mosaic paths directly. diff --git a/packages/mosaic/framework/profiles/domains/crypto-web3.json b/packages/mosaic/framework/profiles/domains/crypto-web3.json new file mode 100644 index 0000000..36f2aa4 --- /dev/null +++ b/packages/mosaic/framework/profiles/domains/crypto-web3.json @@ -0,0 +1,190 @@ +{ + "name": "Cryptocurrency & Web3 Security", + "description": "Security patterns for blockchain, cryptocurrency, and Web3 applications", + "domainKeywords": [ + "crypto", + "blockchain", + "web3", + "defi", + "nft", + "wallet", + "smart contract", + "ethereum" + ], + "compliance": { + "regulations": ["AML", "KYC", "FATF", "BSA", "Regional crypto regulations"], + "scope": "Applications handling cryptocurrencies and digital assets", + "requirements": [ + "Secure private key management", + "Anti-money laundering (AML) compliance", + "Know Your Customer (KYC) verification", + "Transaction monitoring and reporting", + "Wallet security and multi-signature", + "Smart contract security audits" + ] + }, + "securityPatterns": { + "walletSecurity": { + "privateKeys": "Never store private keys in plaintext", + "keyDerivation": "Use BIP32/BIP44 for key derivation", + "storage": "Hardware Security Modules (HSMs) for production", + "backup": "Secure backup and recovery procedures", + "multiSig": "Multi-signature wallets for high-value transactions" + }, + "smartContracts": { + "auditing": "Professional security audits required", + "testing": "Comprehensive test coverage including edge cases", + "upgradeability": "Consider proxy patterns for upgradeable contracts", + "accessControl": "Role-based access control in contracts", + "gasOptimization": "Optimize for gas efficiency and DoS protection" + }, + "transactionSecurity": { + "validation": "Multi-layer transaction validation", + "monitoring": "Real-time transaction monitoring", + "limits": "Configurable transaction limits", + "timelock": "Time-delayed execution for large transactions", + "approval": "Multi-party approval workflows" + }, + "apiSecurity": { + "authentication": "Strong API authentication (JWT + API keys)", + "rateLimit": "Aggressive rate limiting for trading APIs", + "signing": "Request signing for sensitive operations", + "websockets": "Secure WebSocket connections for real-time data" + } + }, + "implementationPatterns": { + "backend": { + "walletIntegration": { + "abstraction": "Abstract wallet operations behind service layer", + "keyManagement": "Separate key management from application logic", + "transactions": "Queue and batch transactions for efficiency", + "monitoring": "Monitor blockchain for transaction confirmations" + }, + "tradingEngine": { + "orderMatching": "Secure order matching algorithms", + "balanceTracking": "Accurate balance tracking with locks", + "riskManagement": "Position limits and risk controls", + "latency": "Low-latency execution for competitive trading" + }, + "compliance": { + "kyc": "Identity verification workflows", + "aml": "Automated AML screening and monitoring", + "reporting": "Suspicious activity reporting (SAR)", + "sanctions": "OFAC and sanctions list screening" + } + }, + "frontend": { + "walletConnection": { + "webWallets": "Support for MetaMask, WalletConnect, etc.", + "security": "Validate wallet signatures and addresses", + "persistence": "Secure session management", + "switching": "Handle network and account switching" + }, + "trading": { + "realTime": "Real-time price and order book updates", + "charting": "Advanced charting capabilities", + "orderTypes": "Support for various order types", + "riskWarnings": "Clear risk disclosures and warnings" + } + } + }, + "blockchainIntegration": { + "ethereum": { + "web3": "Use ethers.js or web3.js for blockchain interaction", + "infura": "Reliable node access via Infura/Alchemy", + "events": "Event listening and log parsing", + "gasManagement": "Dynamic gas price management" + }, + "bitcoin": { + "addresses": "Support for multiple address types", + "utxo": "UTXO management and coin selection", + "fees": "Dynamic fee estimation", + "scripting": "Advanced scripting for complex transactions" + }, + "multiChain": { + "abstraction": "Chain-agnostic service interfaces", + "bridging": "Cross-chain bridge integrations", + "networks": "Support for testnets and multiple networks", + "consensus": "Handle different consensus mechanisms" + } + }, + "testingRequirements": { + "coverage": { + "minimum": "95% for financial logic modules", + "focus": "Security-critical components and edge cases" + }, + "security": [ + "Smart contract security audits", + "Penetration testing for web interfaces", + "Key management security testing", + "Transaction flow security validation", + "API security testing" + ], + "blockchain": [ + "Test on multiple networks (mainnet, testnet)", + "Handle network congestion scenarios", + "Test transaction failure and retry logic", + "Validate gas estimation accuracy", + "Test blockchain reorganization handling" + ] + }, + "context7Libraries": [ + "ethers", + "web3", + "@metamask/providers", + "bitcoinjs-lib", + "@walletconnect/client", + "bip32", + "bip39" + ], + "codeTemplates": { + "walletService": { + "description": "Secure wallet service interface", + "template": "@Injectable()\nexport class WalletService {\n async signTransaction(transaction: Transaction, keyId: string): Promise {\n const privateKey = await this.keyManager.getKey(keyId);\n return this.signer.sign(transaction, privateKey);\n }\n\n async validateAddress(address: string, network: Network): Promise {\n return this.validator.isValid(address, network);\n }\n}" + }, + "transactionMonitor": { + "description": "Blockchain transaction monitoring", + "template": "this.web3.eth.subscribe('pendingTransactions', (txHash) => {\n this.web3.eth.getTransaction(txHash).then(tx => {\n if (this.isWatchedAddress(tx.to)) {\n this.processIncomingTransaction(tx);\n }\n });\n});" + }, + "smartContractInteraction": { + "description": "Safe smart contract interaction", + "template": "const contract = new ethers.Contract(address, abi, signer);\nconst gasEstimate = await contract.estimateGas.transfer(to, amount);\nconst tx = await contract.transfer(to, amount, {\n gasLimit: gasEstimate.mul(110).div(100), // 10% buffer\n gasPrice: await this.getOptimalGasPrice()\n});" + } + }, + "complianceChecklist": [ + "Know Your Customer (KYC) procedures implemented", + "Anti-Money Laundering (AML) monitoring in place", + "Suspicious activity reporting (SAR) procedures", + "OFAC and sanctions screening implemented", + "Transaction monitoring and analysis tools", + "Customer due diligence (CDD) procedures", + "Enhanced due diligence (EDD) for high-risk customers", + "Record keeping and data retention policies", + "Compliance training for staff", + "Regular compliance audits and reviews" + ], + "securityBestPractices": [ + "Never store private keys in application code", + "Use hardware security modules (HSMs) for key storage", + "Implement multi-signature wallets for treasury management", + "Conduct regular security audits of smart contracts", + "Use time-locked transactions for large amounts", + "Implement comprehensive transaction monitoring", + "Use secure random number generation", + "Validate all blockchain data independently", + "Implement proper access controls and authentication", + "Maintain detailed audit logs of all operations" + ], + "riskAssessment": [ + "Private key compromise and theft", + "Smart contract vulnerabilities and exploits", + "Exchange hacks and loss of user funds", + "Regulatory compliance failures", + "Market manipulation and fraud", + "Technical failures and system outages", + "Insider threats and malicious employees", + "Third-party service provider risks", + "Quantum computing threats to cryptography", + "Cross-chain bridge vulnerabilities" + ] +} diff --git a/packages/mosaic/framework/profiles/domains/fintech-security.json b/packages/mosaic/framework/profiles/domains/fintech-security.json new file mode 100644 index 0000000..1c89836 --- /dev/null +++ b/packages/mosaic/framework/profiles/domains/fintech-security.json @@ -0,0 +1,190 @@ +{ + "name": "Fintech Security Compliance", + "description": "PCI DSS and financial security requirements for fintech applications", + "domainKeywords": [ + "payment", + "financial", + "banking", + "credit", + "debit", + "transaction", + "pci", + "fintech" + ], + "compliance": { + "regulations": ["PCI DSS", "PSD2", "SOX", "KYC", "AML"], + "scope": "Applications processing payment card data", + "requirements": [ + "Secure cardholder data", + "Encrypt transmission of cardholder data", + "Protect stored cardholder data", + "Maintain vulnerability management program", + "Implement strong access control measures", + "Regularly monitor and test networks", + "Maintain information security policy" + ] + }, + "dataClassification": { + "pan": { + "definition": "Primary Account Number (Credit/Debit card number)", + "storage": "Never store full PAN unless absolutely necessary", + "masking": "Show only last 4 digits", + "encryption": "AES-256 if storage required", + "transmission": "Always encrypted with TLS 1.2+" + }, + "sadData": { + "definition": "Sensitive Authentication Data", + "types": ["CVV2", "PIN", "Track data"], + "storage": "Never store SAD after authorization", + "handling": "Process but do not retain" + }, + "cardholderData": { + "definition": "PAN + cardholder name, service code, expiration date", + "minimization": "Store only if business need exists", + "retention": "Purge when no longer needed", + "access": "Restrict access to authorized personnel only" + } + }, + "securityPatterns": { + "encryption": { + "algorithm": "AES-256 for data at rest", + "keyManagement": "Hardware Security Modules (HSMs) preferred", + "transmission": "TLS 1.2+ for data in transit", + "tokenization": "Replace PAN with non-sensitive tokens" + }, + "authentication": { + "mfa": "Multi-factor authentication mandatory", + "passwordPolicy": "Complex passwords, regular rotation", + "sessionManagement": "Secure session handling with timeout", + "biometric": "Support for biometric authentication" + }, + "authorization": { + "rbac": "Role-based access control", + "segregationOfDuties": "Separate roles for sensitive operations", + "leastPrivilege": "Minimum necessary access principle", + "approval": "Multi-person approval for high-value transactions" + }, + "fraudPrevention": { + "riskScoring": "Real-time transaction risk assessment", + "monitoring": "Anomaly detection and behavioral analytics", + "alerts": "Immediate alerts for suspicious activities", + "blocking": "Automatic blocking of fraudulent transactions" + } + }, + "implementationPatterns": { + "backend": { + "paymentProcessing": { + "tokenization": "Use payment tokens instead of card data", + "validation": "Validate all payment inputs", + "logging": "Log transactions without sensitive data", + "encryption": "Encrypt cardholder data before storage" + }, + "apiSecurity": { + "rateLimit": "Implement rate limiting", + "apiKeys": "Secure API key management", + "signing": "Request signing for sensitive operations", + "monitoring": "Monitor API usage patterns" + }, + "database": { + "encryption": "Database-level encryption for sensitive fields", + "access": "Database access controls and monitoring", + "backup": "Encrypted backups with secure key management", + "masking": "Data masking for non-production environments" + } + }, + "frontend": { + "paymentForms": { + "https": "Always use HTTPS for payment pages", + "validation": "Client-side validation with server confirmation", + "autocomplete": "Disable autocomplete for sensitive fields", + "iframes": "Use secure iframes for payment card input" + }, + "dataHandling": { + "noStorage": "Never store payment data in browser", + "masking": "Mask card numbers in UI", + "timeout": "Session timeout for payment pages", + "clearData": "Clear payment data from memory after use" + } + } + }, + "testingRequirements": { + "coverage": { + "minimum": "90% for payment processing modules", + "focus": "Security controls and fraud prevention" + }, + "security": [ + "Penetration testing quarterly", + "Vulnerability scanning monthly", + "Code review for all payment code", + "Test encryption implementation", + "Validate tokenization process" + ], + "compliance": [ + "PCI DSS compliance validation", + "Test access controls", + "Validate audit logging", + "Test incident response procedures", + "Verify data retention policies" + ] + }, + "context7Libraries": [ + "stripe", + "bcrypt", + "jsonwebtoken", + "helmet", + "express-rate-limit", + "crypto" + ], + "codeTemplates": { + "paymentEntity": { + "description": "Payment entity with tokenization", + "template": "@Entity()\nexport class Payment {\n @Tokenized()\n @Column()\n cardToken: string;\n\n @Column()\n lastFourDigits: string;\n\n @Encrypted()\n @Column()\n amount: number;\n}" + }, + "transactionLog": { + "description": "Secure transaction logging", + "template": "await this.auditService.logTransaction({\n transactionId: transaction.id,\n userId: user.id,\n amount: transaction.amount,\n currency: transaction.currency,\n status: 'COMPLETED',\n riskScore: riskAssessment.score,\n timestamp: new Date()\n});" + }, + "fraudCheck": { + "description": "Fraud prevention check", + "template": "const riskScore = await this.fraudService.assessRisk({\n userId: user.id,\n amount: transaction.amount,\n location: transaction.location,\n deviceFingerprint: request.deviceId\n});\n\nif (riskScore > FRAUD_THRESHOLD) {\n await this.alertService.triggerFraudAlert(transaction);\n}" + } + }, + "complianceChecklist": [ + "Cardholder data is encrypted at rest and in transit", + "Sensitive authentication data is not stored", + "Access to cardholder data is restricted and monitored", + "Strong cryptography and security protocols are used", + "Antivirus software is maintained", + "Secure systems and applications are developed", + "Access to data is restricted by business need-to-know", + "Unique IDs are assigned to each person with computer access", + "Physical access to cardholder data is restricted", + "All access to network resources is logged and monitored", + "Security systems and processes are regularly tested", + "Information security policy is maintained" + ], + "riskAssessment": [ + "Unauthorized access to payment data", + "Data breaches and card data theft", + "Fraud and unauthorized transactions", + "System vulnerabilities and exploits", + "Insider threats and malicious employees", + "Third-party payment processor risks", + "Network security vulnerabilities", + "Application security weaknesses", + "Physical security of payment systems", + "Business continuity and disaster recovery" + ], + "regulatoryReporting": [ + "PCI DSS compliance reports", + "Suspicious activity reports (SARs)", + "Currency transaction reports (CTRs)", + "Know Your Customer (KYC) documentation", + "Anti-Money Laundering (AML) compliance", + "Data breach notification requirements", + "Consumer privacy disclosures", + "Financial audit requirements", + "Incident response documentation", + "Third-party risk assessments" + ] +} diff --git a/packages/mosaic/framework/profiles/domains/healthcare-hipaa.json b/packages/mosaic/framework/profiles/domains/healthcare-hipaa.json new file mode 100644 index 0000000..92b9072 --- /dev/null +++ b/packages/mosaic/framework/profiles/domains/healthcare-hipaa.json @@ -0,0 +1,189 @@ +{ + "name": "Healthcare HIPAA Compliance", + "description": "HIPAA compliance requirements for healthcare applications handling PHI", + "domainKeywords": ["health", "medical", "patient", "hipaa", "phi", "healthcare"], + "compliance": { + "regulation": "HIPAA (Health Insurance Portability and Accountability Act)", + "scope": "All applications handling Protected Health Information (PHI)", + "requirements": [ + "Encrypt PHI at rest and in transit", + "Implement access controls for PHI", + "Audit all access to PHI", + "Ensure data integrity", + "Implement proper user authentication", + "Maintain data minimization practices" + ] + }, + "dataClassification": { + "phi": { + "definition": "Individually identifiable health information", + "examples": [ + "Names, addresses, birth dates", + "Phone numbers, email addresses", + "Social Security numbers", + "Medical record numbers", + "Health plan beneficiary numbers", + "Account numbers", + "Certificate/license numbers", + "Vehicle identifiers and serial numbers", + "Device identifiers and serial numbers", + "Web Universal Resource Locators (URLs)", + "Internet Protocol (IP) address numbers", + "Biometric identifiers", + "Full face photographic images", + "Medical diagnoses and treatment information", + "Lab results and vital signs" + ], + "encryption": "AES-256 encryption required", + "storage": "Must be encrypted at rest", + "transmission": "Must be encrypted in transit (TLS 1.2+)" + } + }, + "securityPatterns": { + "encryption": { + "algorithm": "AES-256", + "keyManagement": "Use AWS KMS, Azure Key Vault, or similar", + "implementation": "Field-level encryption for PHI columns", + "example": "@Encrypted decorator for entity fields" + }, + "authentication": { + "method": "Multi-factor authentication required", + "tokenType": "JWT with refresh tokens", + "sessionTimeout": "Maximum 15 minutes inactive timeout", + "passwordPolicy": "Minimum 8 characters, complexity requirements" + }, + "authorization": { + "model": "Role-Based Access Control (RBAC)", + "principle": "Minimum necessary access", + "implementation": "Care group permissions with data segmentation", + "auditTrail": "Log all authorization decisions" + }, + "auditLogging": { + "requirement": "All PHI access must be logged", + "fields": [ + "User ID", + "Patient ID", + "Action performed", + "Timestamp", + "IP address", + "Success/failure", + "Data accessed" + ], + "retention": "6 years minimum", + "integrity": "Logs must be tamper-evident" + } + }, + "implementationPatterns": { + "backend": { + "entities": { + "phiFields": "Mark PHI fields with @PHIEncrypted decorator", + "auditables": "Extend BaseAuditableEntity for PHI entities", + "relationships": "Implement proper access control on relationships" + }, + "controllers": { + "authentication": "All PHI endpoints require authentication", + "authorization": "Check user permissions before PHI access", + "logging": "Log all PHI access attempts", + "validation": "Validate all inputs to prevent injection" + }, + "services": { + "encryption": "Encrypt PHI before database storage", + "decryption": "Decrypt PHI only for authorized access", + "minimization": "Return only necessary PHI fields", + "auditing": "Create audit log entries for all PHI operations" + } + }, + "frontend": { + "dataHandling": { + "localStorage": "Never store PHI in localStorage", + "sessionStorage": "Only encrypted session data allowed", + "memory": "Clear PHI from component state on unmount", + "logging": "Never log PHI to console or external services" + }, + "ui": { + "masking": "Mask sensitive data by default", + "permissions": "Hide UI elements based on user roles", + "timeout": "Implement session timeout with warnings", + "accessibility": "Ensure screen readers don't expose PHI inappropriately" + } + } + }, + "testingRequirements": { + "coverage": { + "minimum": "80% for all PHI-handling modules", + "focus": "Security and privacy controls" + }, + "security": [ + "Test for PHI leakage in API responses", + "Verify encryption of PHI fields", + "Test authorization controls", + "Validate audit logging functionality", + "Test session timeout behavior" + ], + "compliance": [ + "Verify minimum necessary access", + "Test audit log completeness", + "Validate encryption implementation", + "Test user access controls", + "Verify data retention policies" + ] + }, + "context7Libraries": [ + "@nestjs/jwt", + "bcrypt", + "helmet", + "crypto", + "jsonwebtoken", + "express-rate-limit" + ], + "codeTemplates": { + "phiEntity": { + "description": "Entity with PHI fields", + "template": "@Entity()\nexport class Patient {\n @PHIEncrypted()\n @Column()\n firstName: string;\n\n @AuditableField()\n @Column()\n medicalRecordNumber: string;\n}" + }, + "auditLog": { + "description": "Audit log entry", + "template": "await this.auditService.log({\n userId: user.id,\n action: 'VIEW_PATIENT',\n resourceType: 'Patient',\n resourceId: patientId,\n ipAddress: request.ip,\n timestamp: new Date()\n});" + }, + "authGuard": { + "description": "HIPAA auth guard", + "template": "@UseGuards(JwtAuthGuard, RolesGuard)\n@RequirePermission('view_patient_phi')\n@ApiSecurity('bearer')" + } + }, + "complianceChecklist": [ + "All PHI fields are encrypted at rest", + "All PHI transmission uses TLS 1.2+", + "User authentication is implemented with MFA", + "Role-based access control is enforced", + "All PHI access is logged and auditable", + "Session timeout is configured (max 15 minutes)", + "Password policies meet HIPAA requirements", + "Data backup and recovery procedures are secure", + "Incident response procedures are documented", + "Employee access is based on minimum necessary principle" + ], + "riskAssessment": [ + "Unauthorized access to PHI", + "Data breaches due to weak encryption", + "Insider threats and inappropriate access", + "Data loss due to inadequate backups", + "System vulnerabilities and exploits", + "Third-party vendor security risks", + "Physical security of systems and data", + "Network security and access controls", + "Application security vulnerabilities", + "Business continuity and disaster recovery" + ], + "incidentResponse": [ + "Identify and contain the incident", + "Assess the scope and severity", + "Notify affected individuals if required", + "Report to HHS if breach affects 500+ individuals", + "Implement corrective actions", + "Document all incident response activities", + "Conduct post-incident review and lessons learned", + "Update security policies and procedures", + "Provide additional training if needed", + "Monitor for similar incidents" + ] +} diff --git a/packages/mosaic/framework/profiles/tech-stacks/nestjs-backend.json b/packages/mosaic/framework/profiles/tech-stacks/nestjs-backend.json new file mode 100644 index 0000000..a8bcfa8 --- /dev/null +++ b/packages/mosaic/framework/profiles/tech-stacks/nestjs-backend.json @@ -0,0 +1,154 @@ +{ + "name": "NestJS Backend", + "description": "NestJS backend with TypeORM, PostgreSQL, and comprehensive testing", + "filePatterns": ["*.ts", "*.js"], + "excludePatterns": ["*.spec.ts", "*.test.ts", "*.d.ts"], + "techStack": { + "framework": "NestJS", + "language": "TypeScript", + "database": "TypeORM + PostgreSQL", + "validation": "class-validator + class-transformer", + "testing": "Jest + Supertest", + "documentation": "Swagger/OpenAPI", + "caching": "Redis + cache-manager", + "queues": "Bull + Redis" + }, + "conventions": { + "naming": { + "variables": "camelCase", + "functions": "camelCase", + "classes": "PascalCase", + "interfaces": "PascalCase with I prefix", + "types": "PascalCase with T prefix", + "enums": "PascalCase", + "constants": "UPPER_SNAKE_CASE" + }, + "fileStructure": { + "modules": "Feature-based modules in src/{feature}/", + "controllers": "{feature}.controller.ts", + "services": "{feature}.service.ts", + "entities": "{feature}.entity.ts", + "dtos": "dto/{feature}.dto.ts", + "tests": "{feature}.controller.spec.ts, {feature}.service.spec.ts" + }, + "imports": { + "style": "Absolute imports with @ prefix when available", + "grouping": "Third-party, @nestjs, internal, relative", + "sorting": "Alphabetical within groups" + } + }, + "qualityChecks": { + "lint": { + "command": "npx eslint --fix", + "config": "Google TypeScript ESLint config", + "autoFix": true + }, + "format": { + "command": "npx prettier --write", + "config": "80 character line limit", + "autoFix": true + }, + "build": { + "command": "npm run build", + "checkTypes": true, + "failOnError": true + }, + "test": { + "unit": "npm run test:unit", + "integration": "npm run test:integration", + "coverage": "npm run test:cov", + "minimumCoverage": 40 + } + }, + "codePatterns": { + "controller": { + "decorators": ["@Controller", "@ApiTags", "@UseGuards"], + "methods": ["@Get", "@Post", "@Put", "@Delete", "@Patch"], + "responses": ["@ApiResponse", "@ApiOperation"], + "validation": ["@Body", "@Param", "@Query with DTOs"], + "errorHandling": "Use HttpException and custom exception filters" + }, + "service": { + "injection": "Constructor dependency injection with @Injectable", + "methods": "Async methods with proper error handling", + "database": "Use TypeORM repository pattern", + "transactions": "@Transaction decorator for data consistency" + }, + "entity": { + "decorators": ["@Entity", "@PrimaryGeneratedColumn", "@Column"], + "relationships": ["@ManyToOne", "@OneToMany", "@ManyToMany"], + "validation": "class-validator decorators on fields", + "timestamps": "Include createdAt, updatedAt with @CreateDateColumn" + }, + "dto": { + "validation": "class-validator decorators (@IsString, @IsOptional)", + "transformation": "class-transformer decorators (@Transform, @Type)", + "swagger": "Swagger decorators (@ApiProperty, @ApiPropertyOptional)", + "inheritance": "Use PartialType, PickType for variations" + }, + "testing": { + "unit": "Test services and controllers independently with mocks", + "integration": "Test complete request/response cycles", + "mocking": "Use jest.mock for dependencies", + "coverage": "Focus on business logic and edge cases" + } + }, + "context7Libraries": [ + "@nestjs/common", + "@nestjs/core", + "@nestjs/typeorm", + "@nestjs/swagger", + "@nestjs/jwt", + "@nestjs/passport", + "@nestjs/cache-manager", + "@nestjs/throttler", + "typeorm", + "class-validator", + "class-transformer", + "jest" + ], + "commonImports": { + "controller": [ + "import { Controller, Get, Post, Put, Delete, Patch, Body, Param, Query, UseGuards, HttpException, HttpStatus } from '@nestjs/common';", + "import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';" + ], + "service": [ + "import { Injectable } from '@nestjs/common';", + "import { InjectRepository } from '@nestjs/typeorm';", + "import { Repository } from 'typeorm';" + ], + "entity": [ + "import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm';", + "import { IsString, IsOptional, IsNumber, IsBoolean, IsDate } from 'class-validator';" + ], + "dto": [ + "import { IsString, IsOptional, IsNumber, IsBoolean, IsEmail, IsArray } from 'class-validator';", + "import { Transform, Type } from 'class-transformer';", + "import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';" + ] + }, + "bestPractices": [ + "Use dependency injection for all services and repositories", + "Validate all input data using DTOs with class-validator", + "Document all API endpoints with Swagger decorators", + "Implement proper error handling with custom exception filters", + "Use TypeORM repositories for database operations", + "Write unit tests for all services and integration tests for controllers", + "Use environment variables for configuration", + "Implement rate limiting and security guards", + "Use transactions for operations affecting multiple entities", + "Follow REST API conventions for endpoint naming" + ], + "securityConsiderations": [ + "Validate and sanitize all inputs", + "Use JWT authentication with proper token validation", + "Implement role-based access control (RBAC)", + "Use HTTPS in production environments", + "Implement rate limiting to prevent abuse", + "Hash passwords using bcrypt", + "Use parameterized queries to prevent SQL injection", + "Implement proper CORS configuration", + "Log security-relevant events for auditing", + "Use environment variables for sensitive configuration" + ] +} diff --git a/packages/mosaic/framework/profiles/tech-stacks/nextjs-fullstack.json b/packages/mosaic/framework/profiles/tech-stacks/nextjs-fullstack.json new file mode 100644 index 0000000..b9f60f3 --- /dev/null +++ b/packages/mosaic/framework/profiles/tech-stacks/nextjs-fullstack.json @@ -0,0 +1,168 @@ +{ + "name": "Next.js Fullstack", + "description": "Next.js 14+ with App Router, TypeScript, Tailwind CSS, and modern fullstack development", + "filePatterns": ["*.tsx", "*.ts", "*.jsx", "*.js"], + "excludePatterns": ["*.test.tsx", "*.test.ts", "*.spec.tsx", "*.spec.ts", "*.d.ts"], + "techStack": { + "framework": "Next.js 14+ with App Router", + "language": "TypeScript", + "styling": "Tailwind CSS", + "database": "Prisma + PostgreSQL", + "authentication": "NextAuth.js", + "stateManagement": "Zustand + React Query", + "testing": "Jest + React Testing Library", + "deployment": "Vercel", + "api": "Next.js API Routes / Server Actions" + }, + "conventions": { + "naming": { + "components": "PascalCase (UserProfile.tsx)", + "pages": "lowercase with hyphens (user-profile/page.tsx)", + "apiRoutes": "lowercase with hyphens (api/user-profile/route.ts)", + "hooks": "camelCase with use prefix (useAuth.ts)", + "utilities": "camelCase (formatDate.ts)", + "constants": "UPPER_SNAKE_CASE", + "types": "PascalCase with T prefix" + }, + "fileStructure": { + "appRouter": "app/{route}/page.tsx, layout.tsx", + "apiRoutes": "app/api/{endpoint}/route.ts", + "components": "components/{feature}/{ComponentName}.tsx", + "hooks": "hooks/use{HookName}.ts", + "libs": "lib/{utility}.ts", + "types": "types/{feature}.types.ts", + "prisma": "prisma/schema.prisma, prisma/migrations/", + "tests": "__tests__/{ComponentName}.test.tsx" + }, + "imports": { + "style": "Absolute imports with @ prefix", + "grouping": "React/Next, third-party, internal, relative", + "sorting": "Alphabetical within groups" + } + }, + "qualityChecks": { + "lint": { + "command": "npx eslint --fix", + "config": "Next.js ESLint + TypeScript", + "autoFix": true + }, + "format": { + "command": "npx prettier --write", + "config": "80 character line limit", + "autoFix": true + }, + "build": { + "command": "npm run build", + "checkTypes": true, + "failOnError": true + }, + "test": { + "unit": "npm test", + "coverage": "npm run test:coverage", + "minimumCoverage": 75 + } + }, + "codePatterns": { + "page": { + "structure": "Default export function with metadata", + "metadata": "Use generateMetadata for dynamic SEO", + "loading": "Create loading.tsx for loading states", + "error": "Create error.tsx for error boundaries", + "notFound": "Create not-found.tsx for 404 handling" + }, + "layout": { + "structure": "Root layout with html and body tags", + "metadata": "Define default metadata and viewport", + "providers": "Wrap children with necessary providers", + "fonts": "Use next/font for font optimization" + }, + "component": { + "client": "Use 'use client' directive for client components", + "server": "Default to server components when possible", + "props": "Define TypeScript interfaces for props", + "memo": "Use React.memo for performance when needed" + }, + "apiRoute": { + "structure": "Export named functions (GET, POST, etc.)", + "params": "Use typed params and searchParams", + "responses": "Return NextResponse with proper status codes", + "middleware": "Use middleware for auth and validation" + }, + "serverActions": { + "directive": "Use 'use server' directive", + "validation": "Validate input data with zod", + "revalidation": "Use revalidatePath/revalidateTag", + "errors": "Handle errors gracefully" + }, + "database": { + "prisma": "Use Prisma Client for database operations", + "transactions": "Use Prisma transactions for complex operations", + "migrations": "Use Prisma migrate for schema changes", + "seeding": "Create seed scripts for development data" + } + }, + "context7Libraries": [ + "next", + "react", + "@next/font", + "next-auth", + "@prisma/client", + "prisma", + "tailwindcss", + "zustand", + "@tanstack/react-query", + "zod" + ], + "commonImports": { + "page": ["import { Metadata } from 'next';", "import { notFound } from 'next/navigation';"], + "component": [ + "import React from 'react';", + "import Link from 'next/link';", + "import Image from 'next/image';" + ], + "apiRoute": [ + "import { NextRequest, NextResponse } from 'next/server';", + "import { getServerSession } from 'next-auth';" + ], + "serverAction": [ + "import { revalidatePath } from 'next/cache';", + "import { redirect } from 'next/navigation';" + ] + }, + "bestPractices": [ + "Use App Router instead of Pages Router for new projects", + "Default to Server Components, use Client Components only when needed", + "Use Next.js Image component for optimized images", + "Implement proper SEO with metadata API", + "Use Server Actions for form handling and mutations", + "Implement proper error handling with error boundaries", + "Use Prisma for type-safe database operations", + "Implement proper authentication with NextAuth.js", + "Use Tailwind CSS for styling with design system approach", + "Implement proper loading states and skeleton screens" + ], + "seoOptimization": [ + "Use generateMetadata for dynamic meta tags", + "Implement proper Open Graph and Twitter Card tags", + "Use structured data (JSON-LD) where appropriate", + "Implement proper canonical URLs", + "Use Next.js Image component with alt text", + "Implement proper heading hierarchy", + "Use semantic HTML elements", + "Generate sitemap.xml and robots.txt", + "Implement proper internal linking", + "Optimize Core Web Vitals" + ], + "performanceOptimizations": [ + "Use Next.js Image component with proper sizing", + "Implement code splitting with dynamic imports", + "Use React.lazy and Suspense for component lazy loading", + "Optimize fonts with next/font", + "Use streaming with loading.tsx files", + "Implement proper caching strategies", + "Use ISR (Incremental Static Regeneration) when appropriate", + "Optimize bundle size with proper imports", + "Use web workers for heavy computations", + "Implement proper database query optimization" + ] +} diff --git a/packages/mosaic/framework/profiles/tech-stacks/python-fastapi.json b/packages/mosaic/framework/profiles/tech-stacks/python-fastapi.json new file mode 100644 index 0000000..7addfee --- /dev/null +++ b/packages/mosaic/framework/profiles/tech-stacks/python-fastapi.json @@ -0,0 +1,168 @@ +{ + "name": "Python FastAPI", + "description": "FastAPI with SQLAlchemy, Pydantic, and modern Python development practices", + "filePatterns": ["*.py"], + "excludePatterns": ["*_test.py", "*test*.py", "__pycache__/*"], + "techStack": { + "framework": "FastAPI", + "language": "Python 3.9+", + "database": "SQLAlchemy + PostgreSQL", + "validation": "Pydantic", + "testing": "Pytest + httpx", + "documentation": "OpenAPI/Swagger (auto-generated)", + "async": "asyncio + asyncpg", + "serialization": "Pydantic models" + }, + "conventions": { + "naming": { + "variables": "snake_case", + "functions": "snake_case", + "classes": "PascalCase", + "constants": "UPPER_SNAKE_CASE", + "modules": "lowercase_with_underscores", + "packages": "lowercase" + }, + "fileStructure": { + "routers": "app/routers/{feature}.py", + "models": "app/models/{feature}.py", + "schemas": "app/schemas/{feature}.py", + "services": "app/services/{feature}.py", + "database": "app/database.py", + "tests": "tests/test_{feature}.py" + }, + "imports": { + "style": "Absolute imports from project root", + "grouping": "Standard library, third-party, local", + "sorting": "Alphabetical within groups" + } + }, + "qualityChecks": { + "lint": { + "command": "flake8 .", + "config": "PEP 8 compliance", + "autoFix": false + }, + "format": { + "command": "black .", + "config": "88 character line limit", + "autoFix": true + }, + "typeCheck": { + "command": "mypy .", + "config": "Strict type checking", + "autoFix": false + }, + "build": { + "command": "python -m compileall .", + "checkSyntax": true, + "failOnError": true + }, + "test": { + "unit": "pytest tests/", + "coverage": "pytest --cov=app tests/", + "minimumCoverage": 80 + } + }, + "codePatterns": { + "router": { + "structure": "Use APIRouter with proper prefixes and tags", + "endpoints": "Async functions with proper HTTP methods", + "dependencies": "Use Depends() for dependency injection", + "responses": "Type-annotated response models", + "errors": "Use HTTPException for error handling" + }, + "model": { + "sqlalchemy": "Use SQLAlchemy declarative base", + "relationships": "Properly define foreign keys and relationships", + "validation": "Include proper field constraints", + "timestamps": "Include created_at, updated_at fields" + }, + "schema": { + "pydantic": "Use Pydantic BaseModel for request/response schemas", + "validation": "Include proper field validation", + "serialization": "Configure proper serialization options", + "inheritance": "Use inheritance for variations (Create, Update, Response)" + }, + "service": { + "async": "Use async/await for database operations", + "transactions": "Implement proper transaction handling", + "error_handling": "Comprehensive error handling with custom exceptions", + "logging": "Structured logging for debugging and monitoring" + }, + "testing": { + "fixtures": "Use pytest fixtures for test setup", + "client": "Use TestClient for endpoint testing", + "database": "Use separate test database", + "mocking": "Mock external dependencies and services" + } + }, + "context7Libraries": [ + "fastapi", + "sqlalchemy", + "pydantic", + "pytest", + "httpx", + "asyncpg", + "uvicorn", + "alembic" + ], + "commonImports": { + "router": [ + "from fastapi import APIRouter, Depends, HTTPException, status", + "from sqlalchemy.orm import Session", + "from app.database import get_db" + ], + "model": [ + "from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, Boolean", + "from sqlalchemy.ext.declarative import declarative_base", + "from sqlalchemy.orm import relationship", + "from datetime import datetime" + ], + "schema": [ + "from pydantic import BaseModel, EmailStr, validator", + "from typing import Optional, List", + "from datetime import datetime" + ], + "service": [ + "from sqlalchemy.orm import Session", + "from sqlalchemy.exc import IntegrityError", + "from fastapi import HTTPException, status" + ] + }, + "bestPractices": [ + "Use async/await for I/O operations", + "Implement proper dependency injection with Depends()", + "Use Pydantic models for request/response validation", + "Follow PEP 8 style guidelines", + "Use type hints for all functions and variables", + "Implement proper error handling with HTTP status codes", + "Use SQLAlchemy for database operations with proper relationships", + "Write comprehensive tests with pytest", + "Use environment variables for configuration", + "Implement proper logging for debugging and monitoring" + ], + "securityConsiderations": [ + "Validate and sanitize all input data using Pydantic", + "Use proper authentication and authorization mechanisms", + "Hash passwords using secure algorithms (bcrypt)", + "Implement rate limiting to prevent abuse", + "Use HTTPS in production environments", + "Validate JWT tokens properly", + "Use parameterized queries to prevent SQL injection", + "Implement proper CORS configuration", + "Log security-relevant events for auditing", + "Use environment variables for sensitive configuration" + ], + "asyncPatterns": [ + "Use async def for route handlers that perform I/O", + "Use asyncio.gather() for concurrent operations", + "Implement proper connection pooling for database", + "Use async context managers for resource management", + "Handle exceptions properly in async functions", + "Use asyncio.create_task() for background tasks", + "Implement proper shutdown handling for async resources", + "Use async generators for streaming responses", + "Avoid blocking operations in async functions", + "Use proper async testing patterns with pytest-asyncio" + ] +} diff --git a/packages/mosaic/framework/profiles/tech-stacks/react-frontend.json b/packages/mosaic/framework/profiles/tech-stacks/react-frontend.json new file mode 100644 index 0000000..3f276b0 --- /dev/null +++ b/packages/mosaic/framework/profiles/tech-stacks/react-frontend.json @@ -0,0 +1,161 @@ +{ + "name": "React Frontend", + "description": "React 18+ with TypeScript, Tailwind CSS, and modern development practices", + "filePatterns": ["*.tsx", "*.ts", "*.jsx", "*.js"], + "excludePatterns": ["*.test.tsx", "*.test.ts", "*.spec.tsx", "*.spec.ts", "*.d.ts"], + "techStack": { + "framework": "React 18+", + "language": "TypeScript", + "styling": "Tailwind CSS", + "stateManagement": "React Query + Context API", + "routing": "React Router", + "testing": "React Testing Library + Jest", + "bundler": "Create React App / Vite", + "icons": "Heroicons + Lucide React", + "charts": "Chart.js + react-chartjs-2" + }, + "conventions": { + "naming": { + "components": "PascalCase (UserProfile.tsx)", + "hooks": "camelCase with use prefix (useAuth.ts)", + "utilities": "camelCase (formatDate.ts)", + "constants": "UPPER_SNAKE_CASE", + "types": "PascalCase with T prefix", + "interfaces": "PascalCase with I prefix" + }, + "fileStructure": { + "components": "src/components/{feature}/{ComponentName}.tsx", + "hooks": "src/hooks/use{HookName}.ts", + "services": "src/services/{feature}.service.ts", + "types": "src/types/{feature}.types.ts", + "contexts": "src/contexts/{Feature}Context.tsx", + "pages": "src/pages/{PageName}.tsx", + "tests": "src/components/{feature}/__tests__/{ComponentName}.test.tsx" + }, + "imports": { + "style": "Absolute imports with @ prefix when available", + "grouping": "React, third-party, internal, relative", + "sorting": "Alphabetical within groups" + } + }, + "qualityChecks": { + "lint": { + "command": "npx eslint --fix", + "config": "ESLint React + TypeScript + a11y", + "autoFix": true + }, + "format": { + "command": "npx prettier --write", + "config": "80 character line limit, single quotes", + "autoFix": true + }, + "build": { + "command": "npm run build", + "checkTypes": true, + "failOnError": true + }, + "test": { + "unit": "npm test", + "coverage": "npm run test:coverage", + "minimumCoverage": 70 + } + }, + "codePatterns": { + "component": { + "structure": "Functional components with TypeScript interfaces", + "props": "Define interface for component props", + "state": "Use useState, useReducer for local state", + "effects": "Use useEffect with proper cleanup", + "memo": "Use React.memo for performance optimization when needed", + "forwardRef": "Use forwardRef for components that need ref access" + }, + "hooks": { + "custom": "Extract reusable logic into custom hooks", + "naming": "Always start with 'use' prefix", + "dependencies": "Properly declare useEffect dependencies", + "cleanup": "Return cleanup functions from useEffect when needed" + }, + "styling": { + "tailwind": "Use Tailwind utility classes", + "responsive": "Mobile-first responsive design", + "darkMode": "Support dark mode with Tailwind dark: prefix", + "accessibility": "Include proper ARIA labels and keyboard navigation" + }, + "stateManagement": { + "local": "useState for component-local state", + "global": "Context API for app-wide state", + "server": "React Query for server state management", + "forms": "Controlled components with validation" + }, + "testing": { + "render": "Use @testing-library/react render method", + "queries": "Use semantic queries (getByRole, getByLabelText)", + "userEvents": "Use @testing-library/user-event for interactions", + "mocking": "Mock external dependencies and API calls", + "accessibility": "Test with screen reader and keyboard navigation" + } + }, + "context7Libraries": [ + "react", + "react-dom", + "react-router-dom", + "@tanstack/react-query", + "tailwindcss", + "@testing-library/react", + "@testing-library/user-event", + "@heroicons/react", + "lucide-react", + "chart.js" + ], + "commonImports": { + "component": [ + "import React, { useState, useEffect, useCallback, useMemo } from 'react';", + "import { useNavigate, useParams } from 'react-router-dom';" + ], + "hook": [ + "import { useState, useEffect, useCallback, useContext } from 'react';", + "import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';" + ], + "test": [ + "import { render, screen, fireEvent, waitFor } from '@testing-library/react';", + "import userEvent from '@testing-library/user-event';", + "import { BrowserRouter } from 'react-router-dom';" + ] + }, + "bestPractices": [ + "Use functional components with hooks instead of class components", + "Extract custom hooks for reusable stateful logic", + "Use React.memo for performance optimization when appropriate", + "Implement proper error boundaries for error handling", + "Use React Query for server state management", + "Follow accessibility guidelines (WCAG 2.1)", + "Implement responsive design with mobile-first approach", + "Use TypeScript for type safety", + "Write comprehensive tests for components and hooks", + "Optimize bundle size with code splitting and lazy loading" + ], + "accessibilityRequirements": [ + "Provide meaningful alt text for images", + "Use semantic HTML elements", + "Ensure proper heading hierarchy (h1, h2, h3)", + "Include ARIA labels for interactive elements", + "Support keyboard navigation for all interactive elements", + "Maintain sufficient color contrast ratios", + "Provide focus indicators for keyboard users", + "Use role attributes when semantic HTML is insufficient", + "Test with screen readers", + "Ensure form fields have associated labels" + ], + "performanceOptimizations": [ + "Use React.lazy for code splitting", + "Implement virtualization for long lists", + "Use useMemo and useCallback to prevent unnecessary re-renders", + "Optimize images with proper formats and sizes", + "Use React.memo for components that receive stable props", + "Implement proper error boundaries", + "Use React Query for efficient data fetching and caching", + "Minimize bundle size by importing only needed modules", + "Use web workers for heavy computations", + "Implement proper loading states and skeleton screens" + ] +} diff --git a/packages/mosaic/framework/profiles/workflows/api-development.json b/packages/mosaic/framework/profiles/workflows/api-development.json new file mode 100644 index 0000000..72d56f1 --- /dev/null +++ b/packages/mosaic/framework/profiles/workflows/api-development.json @@ -0,0 +1,182 @@ +{ + "name": "API Development Workflow", + "description": "Standardized workflow for REST/GraphQL API endpoint development", + "workflowType": "api-development", + "applicablePatterns": ["REST", "GraphQL", "WebSocket"], + "phases": { + "planning": { + "description": "API design and specification phase", + "activities": [ + "Define API contract and OpenAPI specification", + "Design request/response schemas", + "Plan error handling and status codes", + "Consider rate limiting and pagination", + "Document authentication and authorization requirements" + ] + }, + "implementation": { + "description": "Core API implementation phase", + "activities": [ + "Create controller/resolver with proper routing", + "Implement service layer with business logic", + "Add input validation and sanitization", + "Implement proper error handling", + "Add authentication and authorization guards" + ] + }, + "testing": { + "description": "Comprehensive API testing phase", + "activities": [ + "Write unit tests for service layer", + "Create integration tests for endpoints", + "Test error scenarios and edge cases", + "Validate API documentation accuracy", + "Perform security testing" + ] + }, + "documentation": { + "description": "API documentation and examples", + "activities": [ + "Generate/update OpenAPI documentation", + "Create usage examples and tutorials", + "Document rate limits and quotas", + "Add error code documentation", + "Update API versioning information" + ] + } + }, + "implementationPatterns": { + "controller": { + "structure": "Thin controller with business logic in services", + "validation": "Use DTOs for input validation", + "responses": "Standardized response format", + "errors": "Consistent error handling middleware", + "documentation": "Comprehensive API documentation decorators" + }, + "service": { + "business_logic": "Core business logic implementation", + "data_access": "Repository pattern for data operations", + "transactions": "Database transaction management", + "caching": "Implement caching where appropriate", + "external_apis": "Handle external API integrations" + }, + "validation": { + "input": "Validate all input parameters and body data", + "sanitization": "Sanitize inputs to prevent injection attacks", + "authorization": "Verify user permissions for operations", + "rate_limiting": "Implement appropriate rate limiting", + "idempotency": "Support idempotent operations where needed" + }, + "responses": { + "success": "Consistent success response format", + "errors": "Standardized error response structure", + "pagination": "Implement cursor or offset pagination", + "filtering": "Support query filtering and sorting", + "versioning": "Handle API versioning appropriately" + } + }, + "qualityGates": { + "pre_implementation": [ + "API specification reviewed and approved", + "Data models and schemas defined", + "Authentication requirements clarified", + "Rate limiting strategy determined", + "Error handling approach documented" + ], + "implementation": [ + "Code follows established patterns and conventions", + "Input validation implemented for all parameters", + "Proper error handling and logging added", + "Authentication and authorization enforced", + "Business logic separated from controller logic" + ], + "testing": [ + "Unit tests cover all service methods", + "Integration tests validate API contracts", + "Error scenarios properly tested", + "Performance tests pass acceptance criteria", + "Security tests identify no critical vulnerabilities" + ], + "deployment": [ + "API documentation is accurate and complete", + "Monitoring and alerting configured", + "Database migrations applied successfully", + "Configuration validated in target environment", + "Rollback procedures documented and tested" + ] + }, + "testingStrategy": { + "unit_tests": { + "scope": "Individual service methods and business logic", + "mocking": "Mock external dependencies and database", + "coverage": "Minimum 80% code coverage", + "focus": "Business logic and edge cases" + }, + "integration_tests": { + "scope": "Full API endpoint testing with real database", + "scenarios": "Happy path and error scenarios", + "data": "Use test fixtures and factories", + "cleanup": "Clean up test data after each test" + }, + "contract_tests": { + "scope": "API contract validation", + "tools": "OpenAPI validation and contract testing", + "versioning": "Backward compatibility testing", + "documentation": "Ensure examples work correctly" + }, + "performance_tests": { + "scope": "Load and stress testing", + "metrics": "Response time, throughput, resource usage", + "scenarios": "Normal and peak load conditions", + "bottlenecks": "Identify and address performance issues" + }, + "security_tests": { + "scope": "Authentication, authorization, and input validation", + "scenarios": "SQL injection, XSS, authentication bypass", + "tools": "Automated security scanning", + "compliance": "Ensure regulatory compliance requirements" + } + }, + "codeTemplates": { + "restController": { + "framework": "universal", + "template": "// REST Controller Template\n@Controller('/api/v1/users')\n@ApiTags('users')\nexport class UsersController {\n constructor(private usersService: UsersService) {}\n\n @Get()\n @ApiOperation({ summary: 'Get all users' })\n @ApiResponse({ status: 200, description: 'Users retrieved successfully' })\n async getUsers(@Query() query: GetUsersDto): Promise> {\n const users = await this.usersService.getUsers(query);\n return { data: users, message: 'Users retrieved successfully' };\n }\n\n @Post()\n @ApiOperation({ summary: 'Create new user' })\n @ApiResponse({ status: 201, description: 'User created successfully' })\n async createUser(@Body() createUserDto: CreateUserDto): Promise> {\n const user = await this.usersService.createUser(createUserDto);\n return { data: user, message: 'User created successfully' };\n }\n}" + }, + "serviceLayer": { + "framework": "universal", + "template": "// Service Layer Template\n@Injectable()\nexport class UsersService {\n constructor(private usersRepository: UsersRepository) {}\n\n async getUsers(query: GetUsersDto): Promise {\n try {\n const users = await this.usersRepository.findWithFilters(query);\n return users;\n } catch (error) {\n throw new ServiceException('Failed to retrieve users', error);\n }\n }\n\n async createUser(createUserDto: CreateUserDto): Promise {\n try {\n const existingUser = await this.usersRepository.findByEmail(createUserDto.email);\n if (existingUser) {\n throw new ConflictException('User with this email already exists');\n }\n \n const user = await this.usersRepository.create(createUserDto);\n return user;\n } catch (error) {\n throw new ServiceException('Failed to create user', error);\n }\n }\n}" + }, + "integrationTest": { + "framework": "universal", + "template": "// Integration Test Template\ndescribe('Users API', () => {\n let app: TestingModule;\n let httpServer: any;\n\n beforeAll(async () => {\n app = await Test.createTestingModule({\n imports: [AppModule],\n }).compile();\n \n httpServer = app.createNestApplication();\n await httpServer.init();\n });\n\n describe('GET /api/v1/users', () => {\n it('should return users list', async () => {\n const response = await request(httpServer)\n .get('/api/v1/users')\n .expect(200);\n\n expect(response.body.data).toBeInstanceOf(Array);\n expect(response.body.message).toBe('Users retrieved successfully');\n });\n\n it('should handle pagination', async () => {\n const response = await request(httpServer)\n .get('/api/v1/users?page=1&limit=10')\n .expect(200);\n\n expect(response.body.data.length).toBeLessThanOrEqual(10);\n });\n });\n\n describe('POST /api/v1/users', () => {\n it('should create new user', async () => {\n const newUser = {\n name: 'John Doe',\n email: 'john@example.com'\n };\n\n const response = await request(httpServer)\n .post('/api/v1/users')\n .send(newUser)\n .expect(201);\n\n expect(response.body.data.name).toBe(newUser.name);\n expect(response.body.data.email).toBe(newUser.email);\n });\n\n it('should validate required fields', async () => {\n const response = await request(httpServer)\n .post('/api/v1/users')\n .send({})\n .expect(400);\n\n expect(response.body.errors).toBeDefined();\n });\n });\n});" + } + }, + "bestPractices": [ + "Use consistent REST conventions for endpoint naming", + "Implement proper HTTP status codes for different scenarios", + "Add comprehensive input validation and sanitization", + "Use DTOs for request/response data structures", + "Implement proper error handling with meaningful messages", + "Add rate limiting to prevent API abuse", + "Use pagination for endpoints returning large datasets", + "Implement API versioning strategy from the start", + "Add comprehensive logging for debugging and monitoring", + "Use dependency injection for better testability", + "Implement proper authentication and authorization", + "Add API documentation with examples and use cases" + ], + "commonPitfalls": [ + "Putting business logic directly in controllers", + "Not validating input parameters properly", + "Inconsistent error handling and response formats", + "Missing or outdated API documentation", + "Not implementing proper pagination", + "Ignoring rate limiting and abuse prevention", + "Poor error messages that don't help clients", + "Not versioning APIs properly", + "Missing or inadequate logging", + "Not testing error scenarios thoroughly", + "Exposing sensitive information in error responses", + "Not handling database connection failures gracefully" + ] +} diff --git a/packages/mosaic/framework/profiles/workflows/frontend-component.json b/packages/mosaic/framework/profiles/workflows/frontend-component.json new file mode 100644 index 0000000..b1c0ad1 --- /dev/null +++ b/packages/mosaic/framework/profiles/workflows/frontend-component.json @@ -0,0 +1,201 @@ +{ + "name": "Frontend Component Development", + "description": "Standardized workflow for React/Vue component development with accessibility and testing", + "workflowType": "frontend-component", + "applicablePatterns": ["React", "Vue", "Angular", "Web Components"], + "phases": { + "design": { + "description": "Component design and specification phase", + "activities": [ + "Define component API and props interface", + "Create component design system documentation", + "Plan responsive behavior and breakpoints", + "Design accessibility features and ARIA labels", + "Consider component composition and reusability" + ] + }, + "implementation": { + "description": "Core component implementation phase", + "activities": [ + "Create component with TypeScript interfaces", + "Implement responsive styling with CSS/Tailwind", + "Add accessibility features (ARIA, keyboard navigation)", + "Implement component state management", + "Add proper error boundaries and loading states" + ] + }, + "testing": { + "description": "Comprehensive component testing phase", + "activities": [ + "Write unit tests for component logic", + "Create integration tests with user interactions", + "Test accessibility with screen readers", + "Validate responsive behavior across devices", + "Test component with different prop combinations" + ] + }, + "documentation": { + "description": "Component documentation and examples", + "activities": [ + "Create Storybook stories for all variants", + "Document component API and usage examples", + "Add accessibility guidelines and best practices", + "Create interactive documentation", + "Document component performance characteristics" + ] + } + }, + "implementationPatterns": { + "structure": { + "functional": "Use functional components with hooks", + "typescript": "Define proper TypeScript interfaces for props", + "composition": "Design for component composition and reusability", + "separation": "Separate logic, presentation, and styling concerns", + "naming": "Use descriptive and consistent naming conventions" + }, + "styling": { + "responsive": "Mobile-first responsive design approach", + "design_tokens": "Use design tokens for consistency", + "css_modules": "Scoped styling to prevent conflicts", + "accessibility": "Ensure sufficient color contrast and focus indicators", + "dark_mode": "Support light and dark theme variations" + }, + "accessibility": { + "semantic_html": "Use semantic HTML elements when possible", + "aria_labels": "Add appropriate ARIA labels and descriptions", + "keyboard_nav": "Implement full keyboard navigation support", + "screen_readers": "Ensure screen reader compatibility", + "focus_management": "Proper focus management and indicators" + }, + "state_management": { + "local_state": "Use useState for component-local state", + "side_effects": "Use useEffect with proper cleanup", + "performance": "Use useMemo and useCallback for optimization", + "context": "Use React Context for component tree state", + "forms": "Controlled components with proper validation" + }, + "error_handling": { + "boundaries": "Implement error boundaries for error containment", + "validation": "Input validation with user-friendly messages", + "loading_states": "Proper loading and skeleton states", + "fallbacks": "Graceful degradation for component failures", + "user_feedback": "Clear feedback for user actions" + } + }, + "qualityGates": { + "design": [ + "Component API designed with reusability in mind", + "Accessibility requirements identified and documented", + "Responsive behavior planned for all breakpoints", + "Design tokens and styling approach determined", + "Component composition strategy defined" + ], + "implementation": [ + "TypeScript interfaces defined for all props", + "Component implements planned accessibility features", + "Responsive behavior works across all target devices", + "Component follows established coding patterns", + "Error handling and edge cases addressed" + ], + "testing": [ + "Unit tests cover all component logic and edge cases", + "Accessibility tests pass with screen reader testing", + "Integration tests validate user interaction flows", + "Visual regression tests prevent styling issues", + "Performance tests meet established benchmarks" + ], + "documentation": [ + "Storybook stories demonstrate all component variants", + "API documentation is complete and accurate", + "Usage examples and best practices documented", + "Accessibility guidelines provided", + "Performance characteristics documented" + ] + }, + "testingStrategy": { + "unit_tests": { + "scope": "Component logic, prop handling, and state changes", + "tools": "React Testing Library, Jest", + "coverage": "Minimum 85% code coverage", + "focus": "User interactions and business logic" + }, + "accessibility_tests": { + "scope": "ARIA labels, keyboard navigation, screen reader compatibility", + "tools": "axe-core, @testing-library/jest-dom", + "manual": "Manual testing with actual screen readers", + "standards": "WCAG 2.1 AA compliance" + }, + "visual_tests": { + "scope": "Component appearance across different states", + "tools": "Chromatic, Percy, or similar visual testing", + "devices": "Test across multiple device sizes", + "themes": "Test light/dark theme variations" + }, + "integration_tests": { + "scope": "Component behavior within larger application context", + "user_flows": "End-to-end user interaction scenarios", + "data_flow": "Test with real or realistic data", + "performance": "Component performance under load" + }, + "responsive_tests": { + "scope": "Component behavior across different screen sizes", + "breakpoints": "Test all defined responsive breakpoints", + "orientation": "Portrait and landscape orientations", + "devices": "Physical device testing when possible" + } + }, + "codeTemplates": { + "reactComponent": { + "framework": "React", + "template": "import React, { useState, useEffect, useCallback } from 'react';\nimport { cn } from '@/lib/utils';\n\ninterface ComponentNameProps {\n /** Primary content for the component */\n children?: React.ReactNode;\n /** Additional CSS class names */\n className?: string;\n /** Component variant */\n variant?: 'primary' | 'secondary' | 'outline';\n /** Component size */\n size?: 'sm' | 'md' | 'lg';\n /** Disabled state */\n disabled?: boolean;\n /** Click handler */\n onClick?: () => void;\n}\n\n/**\n * ComponentName - Brief description of what this component does\n * \n * @example\n * \n * Content goes here\n * \n */\nexport const ComponentName: React.FC = ({\n children,\n className,\n variant = 'primary',\n size = 'md',\n disabled = false,\n onClick,\n ...props\n}) => {\n const [isActive, setIsActive] = useState(false);\n\n const handleClick = useCallback(() => {\n if (!disabled && onClick) {\n onClick();\n }\n }, [disabled, onClick]);\n\n const handleKeyDown = useCallback((event: React.KeyboardEvent) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n handleClick();\n }\n }, [handleClick]);\n\n return (\n \n {children}\n \n );\n};\n\nComponentName.displayName = 'ComponentName';" + }, + "componentTest": { + "framework": "React Testing Library", + "template": "import { render, screen, fireEvent, waitFor } from '@testing-library/react';\nimport userEvent from '@testing-library/user-event';\nimport { axe, toHaveNoViolations } from 'jest-axe';\nimport { ComponentName } from './ComponentName';\n\n// Extend Jest matchers\nexpect.extend(toHaveNoViolations);\n\ndescribe('ComponentName', () => {\n const user = userEvent.setup();\n\n it('renders with default props', () => {\n render(Test Content);\n \n const button = screen.getByRole('button', { name: 'Test Content' });\n expect(button).toBeInTheDocument();\n expect(button).toHaveClass('bg-primary'); // default variant\n });\n\n it('handles click events', async () => {\n const handleClick = jest.fn();\n render(\n \n Click me\n \n );\n\n const button = screen.getByRole('button', { name: 'Click me' });\n await user.click(button);\n \n expect(handleClick).toHaveBeenCalledTimes(1);\n });\n\n it('supports keyboard navigation', async () => {\n const handleClick = jest.fn();\n render(\n \n Press Enter\n \n );\n\n const button = screen.getByRole('button', { name: 'Press Enter' });\n button.focus();\n \n await user.keyboard('{Enter}');\n expect(handleClick).toHaveBeenCalledTimes(1);\n \n await user.keyboard(' ');\n expect(handleClick).toHaveBeenCalledTimes(2);\n });\n\n it('handles disabled state correctly', async () => {\n const handleClick = jest.fn();\n render(\n \n Disabled\n \n );\n\n const button = screen.getByRole('button', { name: 'Disabled' });\n expect(button).toBeDisabled();\n expect(button).toHaveAttribute('aria-disabled', 'true');\n \n await user.click(button);\n expect(handleClick).not.toHaveBeenCalled();\n });\n\n it('applies correct variant styles', () => {\n const { rerender } = render(\n \n Secondary\n \n );\n \n let button = screen.getByRole('button');\n expect(button).toHaveClass('bg-secondary');\n \n rerender(\n \n Outline\n \n );\n \n button = screen.getByRole('button');\n expect(button).toHaveClass('border');\n });\n\n it('has no accessibility violations', async () => {\n const { container } = render(\n \n Accessible Button\n \n );\n \n const results = await axe(container);\n expect(results).toHaveNoViolations();\n });\n\n it('supports custom className', () => {\n render(\n \n Custom\n \n );\n \n const button = screen.getByRole('button');\n expect(button).toHaveClass('custom-class');\n });\n});" + }, + "storybookStory": { + "framework": "Storybook", + "template": "import type { Meta, StoryObj } from '@storybook/react';\nimport { ComponentName } from './ComponentName';\n\nconst meta: Meta = {\n title: 'Components/ComponentName',\n component: ComponentName,\n parameters: {\n layout: 'centered',\n docs: {\n description: {\n component: 'A versatile button component with multiple variants and sizes.',\n },\n },\n },\n argTypes: {\n variant: {\n control: 'select',\n options: ['primary', 'secondary', 'outline'],\n description: 'The visual variant of the button',\n },\n size: {\n control: 'select', \n options: ['sm', 'md', 'lg'],\n description: 'The size of the button',\n },\n disabled: {\n control: 'boolean',\n description: 'Whether the button is disabled',\n },\n onClick: {\n action: 'clicked',\n description: 'Function called when button is clicked',\n },\n },\n};\n\nexport default meta;\ntype Story = StoryObj;\n\n// Default story\nexport const Default: Story = {\n args: {\n children: 'Button',\n variant: 'primary',\n size: 'md',\n disabled: false,\n },\n};\n\n// Variants showcase\nexport const Variants: Story = {\n render: () => (\n
\n Primary\n Secondary\n Outline\n
\n ),\n};\n\n// Sizes showcase\nexport const Sizes: Story = {\n render: () => (\n
\n Small\n Medium\n Large\n
\n ),\n};\n\n// Disabled state\nexport const Disabled: Story = {\n args: {\n children: 'Disabled Button',\n disabled: true,\n },\n};\n\n// Interactive example\nexport const Interactive: Story = {\n render: () => {\n const [count, setCount] = React.useState(0);\n return (\n
\n

Count: {count}

\n setCount(count + 1)}>\n Increment\n \n
\n );\n },\n};" + } + }, + "accessibilityRequirements": [ + "Use semantic HTML elements when possible (button, input, etc.)", + "Provide meaningful alt text for images", + "Ensure sufficient color contrast (4.5:1 for normal text)", + "Support keyboard navigation for all interactive elements", + "Use ARIA labels and descriptions where needed", + "Implement proper focus management and indicators", + "Support screen readers with appropriate ARIA attributes", + "Test with actual assistive technologies", + "Provide skip links for navigation", + "Use proper heading hierarchy", + "Ensure form labels are properly associated", + "Implement error states with clear messaging" + ], + "performanceOptimizations": [ + "Use React.memo for components that receive stable props", + "Implement useMemo for expensive calculations", + "Use useCallback for event handlers passed to child components", + "Optimize images with proper formats and lazy loading", + "Implement virtualization for large lists", + "Use code splitting for large components", + "Minimize bundle size by importing only needed modules", + "Use CSS-in-JS efficiently to avoid style recalculations", + "Implement proper error boundaries to prevent crashes", + "Monitor component re-render frequency and optimize" + ], + "bestPractices": [ + "Design components for reusability and composition", + "Use TypeScript for better development experience and catch errors", + "Follow accessibility guidelines from the start", + "Write comprehensive tests including accessibility tests", + "Document components with clear examples and usage", + "Use consistent naming conventions across components", + "Implement proper error handling and loading states", + "Consider mobile-first responsive design", + "Use design tokens for consistent styling", + "Optimize for performance without premature optimization", + "Follow the principle of least privilege for component APIs", + "Use proper semantic HTML for better accessibility" + ] +} diff --git a/packages/mosaic/framework/profiles/workflows/testing-automation.json b/packages/mosaic/framework/profiles/workflows/testing-automation.json new file mode 100644 index 0000000..dadfcff --- /dev/null +++ b/packages/mosaic/framework/profiles/workflows/testing-automation.json @@ -0,0 +1,201 @@ +{ + "name": "Testing Automation Workflow", + "description": "Comprehensive testing workflow for unit, integration, and end-to-end testing", + "workflowType": "testing-automation", + "applicablePatterns": [ + "Unit Testing", + "Integration Testing", + "E2E Testing", + "Performance Testing" + ], + "phases": { + "planning": { + "description": "Test planning and strategy phase", + "activities": [ + "Define testing strategy and coverage goals", + "Identify critical paths and edge cases", + "Plan test data and fixtures", + "Define testing environments and CI/CD integration", + "Establish quality gates and acceptance criteria" + ] + }, + "implementation": { + "description": "Test implementation phase", + "activities": [ + "Write unit tests for individual functions and components", + "Create integration tests for API endpoints and workflows", + "Implement end-to-end tests for user journeys", + "Set up test data factories and fixtures", + "Configure test environments and mocking" + ] + }, + "automation": { + "description": "Test automation and CI/CD integration", + "activities": [ + "Integrate tests into CI/CD pipeline", + "Set up parallel test execution", + "Configure test reporting and notifications", + "Implement test result analysis and trending", + "Set up automated test maintenance" + ] + }, + "monitoring": { + "description": "Test monitoring and maintenance phase", + "activities": [ + "Monitor test execution metrics and trends", + "Maintain test suites and remove flaky tests", + "Update tests for new features and changes", + "Analyze test coverage and identify gaps", + "Optimize test execution performance" + ] + } + }, + "testingLevels": { + "unit": { + "scope": "Individual functions, methods, and components in isolation", + "goals": "Fast feedback, high coverage, isolated testing", + "tools": "Jest, Vitest, Mocha, Jasmine", + "coverage": "80%+ for business logic", + "characteristics": "Fast (<1s), Isolated, Repeatable, Self-validating" + }, + "integration": { + "scope": "Interaction between multiple components or services", + "goals": "Verify component integration and data flow", + "tools": "Supertest, TestContainers, Testing Library", + "coverage": "Critical integration points", + "characteristics": "Moderate speed, Real dependencies, Contract validation" + }, + "contract": { + "scope": "API contracts between services", + "goals": "Ensure API compatibility and prevent breaking changes", + "tools": "Pact, OpenAPI validators, Postman", + "coverage": "All public APIs", + "characteristics": "Consumer-driven, Version compatibility, Schema validation" + }, + "e2e": { + "scope": "Complete user workflows from start to finish", + "goals": "Validate critical user journeys work end-to-end", + "tools": "Playwright, Cypress, Selenium", + "coverage": "Critical business flows", + "characteristics": "Slow, Real browser, Full system testing" + }, + "performance": { + "scope": "System performance under various load conditions", + "goals": "Ensure performance requirements are met", + "tools": "Artillery, K6, JMeter, Lighthouse", + "coverage": "Critical performance paths", + "characteristics": "Load testing, Stress testing, Performance monitoring" + } + }, + "testingPatterns": { + "aaa": { + "name": "Arrange, Act, Assert", + "description": "Structure tests with clear setup, execution, and verification", + "example": "// Arrange\nconst user = createTestUser();\n// Act\nconst result = await service.createUser(user);\n// Assert\nexpect(result.id).toBeDefined();" + }, + "given_when_then": { + "name": "Given, When, Then (BDD)", + "description": "Behavior-driven testing with clear preconditions, actions, and outcomes", + "example": "describe('User registration', () => {\n it('should create user when valid data provided', async () => {\n // Given\n const userData = validUserData();\n // When\n const user = await userService.register(userData);\n // Then\n expect(user).toMatchObject(userData);\n });\n});" + }, + "test_doubles": { + "name": "Test Doubles (Mocks, Stubs, Spies)", + "description": "Use test doubles to isolate system under test", + "types": { + "mock": "Verify interactions with dependencies", + "stub": "Provide controlled responses", + "spy": "Monitor calls to real objects", + "fake": "Working implementation with shortcuts" + } + }, + "data_driven": { + "name": "Data-Driven Testing", + "description": "Test same logic with multiple input datasets", + "example": "test.each([\n ['valid@email.com', true],\n ['invalid-email', false],\n ['', false]\n])('validates email %s as %s', (email, expected) => {\n expect(isValidEmail(email)).toBe(expected);\n});" + } + }, + "qualityGates": { + "coverage": { + "unit_tests": "80% minimum for business logic", + "integration_tests": "All critical integration points covered", + "e2e_tests": "All critical user journeys covered", + "mutation_testing": "70% mutation score for critical components" + }, + "performance": { + "test_execution": "Unit tests <5 minutes, Integration <15 minutes", + "feedback_time": "Developer feedback within 10 minutes", + "parallel_execution": "Tests run in parallel where possible", + "flaky_tests": "<1% flaky test rate" + }, + "quality": { + "test_reliability": "95% pass rate on consecutive runs", + "test_maintainability": "Tests updated with code changes", + "test_readability": "Tests serve as documentation", + "test_isolation": "Tests don't depend on each other" + } + }, + "codeTemplates": { + "unitTest": { + "framework": "Jest/Vitest", + "template": "import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';\nimport { UserService } from './UserService';\nimport { MockUserRepository } from './__mocks__/UserRepository';\n\ndescribe('UserService', () => {\n let userService: UserService;\n let mockRepository: MockUserRepository;\n\n beforeEach(() => {\n mockRepository = new MockUserRepository();\n userService = new UserService(mockRepository);\n });\n\n afterEach(() => {\n vi.clearAllMocks();\n });\n\n describe('createUser', () => {\n it('should create user with valid data', async () => {\n // Arrange\n const userData = {\n name: 'John Doe',\n email: 'john@example.com',\n password: 'securePassword123'\n };\n const expectedUser = { id: '123', ...userData };\n mockRepository.create.mockResolvedValue(expectedUser);\n\n // Act\n const result = await userService.createUser(userData);\n\n // Assert\n expect(result).toEqual(expectedUser);\n expect(mockRepository.create).toHaveBeenCalledWith(userData);\n expect(mockRepository.create).toHaveBeenCalledTimes(1);\n });\n\n it('should throw error when email already exists', async () => {\n // Arrange\n const userData = {\n name: 'John Doe',\n email: 'existing@example.com',\n password: 'securePassword123'\n };\n mockRepository.findByEmail.mockResolvedValue({ id: '456', email: userData.email });\n\n // Act & Assert\n await expect(userService.createUser(userData))\n .rejects\n .toThrow('User with email already exists');\n \n expect(mockRepository.findByEmail).toHaveBeenCalledWith(userData.email);\n expect(mockRepository.create).not.toHaveBeenCalled();\n });\n\n it('should handle repository errors gracefully', async () => {\n // Arrange\n const userData = {\n name: 'John Doe',\n email: 'john@example.com',\n password: 'securePassword123'\n };\n const dbError = new Error('Database connection failed');\n mockRepository.create.mockRejectedValue(dbError);\n\n // Act & Assert\n await expect(userService.createUser(userData))\n .rejects\n .toThrow('Failed to create user');\n });\n });\n\n describe('getUserById', () => {\n it('should return user when found', async () => {\n // Arrange\n const userId = '123';\n const expectedUser = { id: userId, name: 'John Doe', email: 'john@example.com' };\n mockRepository.findById.mockResolvedValue(expectedUser);\n\n // Act\n const result = await userService.getUserById(userId);\n\n // Assert\n expect(result).toEqual(expectedUser);\n expect(mockRepository.findById).toHaveBeenCalledWith(userId);\n });\n\n it('should return null when user not found', async () => {\n // Arrange\n const userId = '999';\n mockRepository.findById.mockResolvedValue(null);\n\n // Act\n const result = await userService.getUserById(userId);\n\n // Assert\n expect(result).toBeNull();\n expect(mockRepository.findById).toHaveBeenCalledWith(userId);\n });\n });\n});" + }, + "integrationTest": { + "framework": "Supertest + Jest", + "template": "import request from 'supertest';\nimport { Test, TestingModule } from '@nestjs/testing';\nimport { INestApplication } from '@nestjs/common';\nimport { AppModule } from '../src/app.module';\nimport { DatabaseService } from '../src/database/database.service';\nimport { createTestUser, cleanupTestData } from './fixtures/user.fixtures';\n\ndescribe('Users API Integration Tests', () => {\n let app: INestApplication;\n let databaseService: DatabaseService;\n let testUserId: string;\n\n beforeAll(async () => {\n const moduleFixture: TestingModule = await Test.createTestingModule({\n imports: [AppModule],\n }).compile();\n\n app = moduleFixture.createNestApplication();\n databaseService = moduleFixture.get(DatabaseService);\n \n await app.init();\n \n // Set up test data\n const testUser = await createTestUser(databaseService);\n testUserId = testUser.id;\n });\n\n afterAll(async () => {\n // Clean up test data\n await cleanupTestData(databaseService);\n await app.close();\n });\n\n describe('POST /users', () => {\n it('should create a new user', async () => {\n const newUser = {\n name: 'Jane Doe',\n email: 'jane@example.com',\n password: 'securePassword123'\n };\n\n const response = await request(app.getHttpServer())\n .post('/users')\n .send(newUser)\n .expect(201);\n\n expect(response.body).toMatchObject({\n id: expect.any(String),\n name: newUser.name,\n email: newUser.email,\n createdAt: expect.any(String)\n });\n expect(response.body.password).toBeUndefined();\n\n // Verify user was actually created in database\n const createdUser = await databaseService.user.findUnique({\n where: { id: response.body.id }\n });\n expect(createdUser).toBeTruthy();\n expect(createdUser.name).toBe(newUser.name);\n });\n\n it('should return 400 for invalid data', async () => {\n const invalidUser = {\n name: '', // Invalid: empty name\n email: 'invalid-email', // Invalid: bad email format\n password: '123' // Invalid: too short\n };\n\n const response = await request(app.getHttpServer())\n .post('/users')\n .send(invalidUser)\n .expect(400);\n\n expect(response.body.errors).toBeDefined();\n expect(response.body.errors).toContain(\n expect.objectContaining({\n field: 'email',\n message: expect.stringContaining('valid email')\n })\n );\n });\n\n it('should return 409 for duplicate email', async () => {\n const duplicateUser = {\n name: 'John Duplicate',\n email: 'existing@example.com', // Email already exists\n password: 'securePassword123'\n };\n\n await request(app.getHttpServer())\n .post('/users')\n .send(duplicateUser)\n .expect(409);\n });\n });\n\n describe('GET /users/:id', () => {\n it('should return user when found', async () => {\n const response = await request(app.getHttpServer())\n .get(`/users/${testUserId}`)\n .expect(200);\n\n expect(response.body).toMatchObject({\n id: testUserId,\n name: expect.any(String),\n email: expect.any(String),\n createdAt: expect.any(String)\n });\n expect(response.body.password).toBeUndefined();\n });\n\n it('should return 404 for non-existent user', async () => {\n const nonExistentId = '999999';\n \n await request(app.getHttpServer())\n .get(`/users/${nonExistentId}`)\n .expect(404);\n });\n\n it('should return 400 for invalid user id format', async () => {\n await request(app.getHttpServer())\n .get('/users/invalid-id')\n .expect(400);\n });\n });\n\n describe('PUT /users/:id', () => {\n it('should update user successfully', async () => {\n const updateData = {\n name: 'Updated Name',\n email: 'updated@example.com'\n };\n\n const response = await request(app.getHttpServer())\n .put(`/users/${testUserId}`)\n .send(updateData)\n .expect(200);\n\n expect(response.body).toMatchObject({\n id: testUserId,\n name: updateData.name,\n email: updateData.email,\n updatedAt: expect.any(String)\n });\n\n // Verify update in database\n const updatedUser = await databaseService.user.findUnique({\n where: { id: testUserId }\n });\n expect(updatedUser.name).toBe(updateData.name);\n expect(updatedUser.email).toBe(updateData.email);\n });\n });\n});" + }, + "e2eTest": { + "framework": "Playwright", + "template": "import { test, expect } from '@playwright/test';\nimport { LoginPage } from '../pages/LoginPage';\nimport { DashboardPage } from '../pages/DashboardPage';\nimport { UserProfilePage } from '../pages/UserProfilePage';\n\ntest.describe('User Management E2E Tests', () => {\n let loginPage: LoginPage;\n let dashboardPage: DashboardPage;\n let userProfilePage: UserProfilePage;\n\n test.beforeEach(async ({ page }) => {\n loginPage = new LoginPage(page);\n dashboardPage = new DashboardPage(page);\n userProfilePage = new UserProfilePage(page);\n \n // Navigate to application\n await page.goto('/login');\n });\n\n test('complete user registration and profile update flow', async ({ page }) => {\n // Step 1: Register new user\n await loginPage.clickSignUpLink();\n \n const newUser = {\n name: 'Test User',\n email: `test${Date.now()}@example.com`,\n password: 'SecurePassword123!'\n };\n \n await loginPage.fillRegistrationForm(newUser);\n await loginPage.submitRegistration();\n \n // Verify registration success\n await expect(page.locator('[data-testid=\"registration-success\"]'))\n .toBeVisible();\n \n // Step 2: Login with new user\n await loginPage.login(newUser.email, newUser.password);\n \n // Verify successful login and dashboard access\n await expect(dashboardPage.welcomeMessage)\n .toContainText(`Welcome, ${newUser.name}`);\n \n // Step 3: Navigate to profile settings\n await dashboardPage.clickProfileMenu();\n await dashboardPage.clickProfileSettings();\n \n // Verify profile page loaded\n await expect(userProfilePage.profileForm).toBeVisible();\n \n // Step 4: Update profile information\n const updatedInfo = {\n name: 'Updated Test User',\n bio: 'This is my updated bio',\n phone: '+1234567890'\n };\n \n await userProfilePage.updateProfile(updatedInfo);\n await userProfilePage.saveChanges();\n \n // Verify update success\n await expect(userProfilePage.successMessage)\n .toContainText('Profile updated successfully');\n \n // Step 5: Verify changes persist after page reload\n await page.reload();\n \n await expect(userProfilePage.nameField)\n .toHaveValue(updatedInfo.name);\n await expect(userProfilePage.bioField)\n .toHaveValue(updatedInfo.bio);\n await expect(userProfilePage.phoneField)\n .toHaveValue(updatedInfo.phone);\n \n // Step 6: Test profile picture upload\n await userProfilePage.uploadProfilePicture('./fixtures/test-avatar.jpg');\n \n // Verify image upload\n await expect(userProfilePage.profileImage)\n .toBeVisible();\n \n // Step 7: Test account security settings\n await userProfilePage.clickSecurityTab();\n \n // Change password\n await userProfilePage.changePassword(\n newUser.password,\n 'NewSecurePassword123!'\n );\n \n await expect(userProfilePage.successMessage)\n .toContainText('Password changed successfully');\n \n // Step 8: Test logout and login with new password\n await dashboardPage.logout();\n \n await loginPage.login(newUser.email, 'NewSecurePassword123!');\n \n // Verify successful login with new password\n await expect(dashboardPage.welcomeMessage)\n .toContainText(`Welcome, ${updatedInfo.name}`);\n });\n\n test('should handle profile update errors gracefully', async ({ page }) => {\n // Login as existing user\n await loginPage.login('existing@example.com', 'password123');\n \n // Navigate to profile\n await dashboardPage.navigateToProfile();\n \n // Try to update with invalid data\n await userProfilePage.fillName(''); // Empty name should fail\n await userProfilePage.fillEmail('invalid-email'); // Invalid email\n await userProfilePage.saveChanges();\n \n // Verify error messages\n await expect(userProfilePage.nameError)\n .toContainText('Name is required');\n await expect(userProfilePage.emailError)\n .toContainText('Please enter a valid email');\n \n // Verify form wasn't submitted\n await expect(userProfilePage.successMessage)\n .not.toBeVisible();\n });\n\n test('should be accessible with keyboard navigation', async ({ page }) => {\n await loginPage.login('existing@example.com', 'password123');\n await dashboardPage.navigateToProfile();\n \n // Test keyboard navigation through form\n await page.keyboard.press('Tab'); // Focus name field\n await expect(userProfilePage.nameField).toBeFocused();\n \n await page.keyboard.press('Tab'); // Focus email field\n await expect(userProfilePage.emailField).toBeFocused();\n \n await page.keyboard.press('Tab'); // Focus bio field\n await expect(userProfilePage.bioField).toBeFocused();\n \n // Test form submission with keyboard\n await userProfilePage.fillName('Keyboard User');\n await page.keyboard.press('Enter'); // Should submit form\n \n await expect(userProfilePage.successMessage)\n .toContainText('Profile updated successfully');\n });\n});" + } + }, + "testDataManagement": { + "fixtures": { + "description": "Pre-defined test data for consistent testing", + "patterns": "Factory pattern, Builder pattern, Object mothers", + "storage": "JSON files, Database seeds, In-memory objects" + }, + "factories": { + "description": "Dynamic test data generation", + "tools": "Faker.js, Factory Bot, Factory Girl", + "benefits": "Unique data, Customizable, Realistic" + }, + "mocking": { + "description": "Fake implementations for external dependencies", + "types": "API mocks, Database mocks, Service mocks", + "tools": "MSW, Nock, Sinon, Jest mocks" + }, + "cleanup": { + "description": "Clean up test data after test execution", + "strategies": "Database transactions, Cleanup hooks, Isolated test databases", + "importance": "Prevent test interference, Maintain test isolation" + } + }, + "bestPractices": [ + "Write tests first (TDD) or alongside implementation", + "Keep tests independent and isolated from each other", + "Use descriptive test names that explain the scenario", + "Follow the AAA pattern (Arrange, Act, Assert)", + "Mock external dependencies to ensure test isolation", + "Write both positive and negative test cases", + "Keep tests simple and focused on one thing", + "Use test data factories for consistent test data", + "Clean up test data after test execution", + "Run tests frequently during development", + "Maintain tests as first-class code with proper refactoring", + "Use code coverage as a guide, not a target" + ], + "antiPatterns": [ + "Tests that depend on other tests or test order", + "Tests that test implementation details instead of behavior", + "Over-mocking that makes tests brittle", + "Tests with unclear or generic names", + "Tests that are too complex or test multiple things", + "Ignoring or commenting out failing tests", + "Tests that duplicate production logic", + "Hard-coded test data that becomes outdated", + "Tests that require manual setup or intervention", + "Flaky tests that pass/fail randomly", + "Tests that take too long to run", + "Tests without proper assertions" + ] +} diff --git a/packages/mosaic/framework/remote-install.ps1 b/packages/mosaic/framework/remote-install.ps1 new file mode 100644 index 0000000..635ab11 --- /dev/null +++ b/packages/mosaic/framework/remote-install.ps1 @@ -0,0 +1,38 @@ +# Mosaic Bootstrap — Remote Installer (Windows PowerShell) +# +# One-liner: +# irm https://git.mosaicstack.dev/mosaic/bootstrap/raw/branch/main/remote-install.ps1 | iex +# +# Or explicit: +# powershell -ExecutionPolicy Bypass -File remote-install.ps1 +# +$ErrorActionPreference = "Stop" + +$BootstrapRef = if ($env:MOSAIC_BOOTSTRAP_REF) { $env:MOSAIC_BOOTSTRAP_REF } else { "main" } +$ArchiveUrl = "https://git.mosaicstack.dev/mosaic/bootstrap/archive/$BootstrapRef.zip" +$WorkDir = Join-Path $env:TEMP "mosaic-bootstrap-$PID" +$ZipPath = "$WorkDir.zip" + +try { + Write-Host "[mosaic] Downloading bootstrap archive (ref: $BootstrapRef)..." + New-Item -ItemType Directory -Path $WorkDir -Force | Out-Null + Invoke-WebRequest -Uri $ArchiveUrl -OutFile $ZipPath -UseBasicParsing + + Write-Host "[mosaic] Extracting..." + Expand-Archive -Path $ZipPath -DestinationPath $WorkDir -Force + + $InstallScript = Join-Path $WorkDir "bootstrap\install.ps1" + if (-not (Test-Path $InstallScript)) { + throw "install.ps1 not found in archive" + } + + Write-Host "[mosaic] Running install..." + & $InstallScript + + Write-Host "[mosaic] Done." +} +finally { + Write-Host "[mosaic] Cleaning up temporary files..." + Remove-Item -Path $ZipPath -Force -ErrorAction SilentlyContinue + Remove-Item -Path $WorkDir -Recurse -Force -ErrorAction SilentlyContinue +}