From ee1f0f6d8f87933418b15d79732f17a1172d7486 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 4 Apr 2026 13:46:29 +0000 Subject: [PATCH 1/3] fix(phase-3): normalize workflows and fix CLI command references MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 3 workflow normalization (#218): - Fix label mismatch: all-progress and init.ts queried 'maxsim:phase' instead of 'type:phase' (the label actually created by create-phase) (#388) - Fix delete-comments to support comma-separated types for multi-type deletion (e.g. --type plan,context,research) (#389) - Add --plan-number flag to post-comment, allowing it to subsume post-plan-comment functionality (#390) - Replace set-status with move-issue in all workflow files, fix set-project --number to --project-number (#391) - Implement validate-structure and config-save-defaults commands, replace ghost refs (phase complete, install write-claude-md) (#392) - Replace ~15 direct gh CLI calls across 8 workflow files with maxsim-tools.cjs wrappers (#393) - Remove set-status alias from GITHUB_COMMANDS (#394) - Wire self-improvement.md into improve.md, verification-patterns.md into verify-phase.md (#395) - Remove stale hasPhaseFiles() from statusline hook — config-only approach, no local filesystem phase detection (#396) - Add template sync module (install/sync.ts) with hash-based one-way sync from bundled templates to .claude/ (#397) - Extend workflow validation test with CLI command reference checking and workflow file reference validation (#398) https://claude.ai/code/session_01EMccZLcggSgkXGywamtcuk --- packages/cli/src/commands/config.ts | 44 ++++++ packages/cli/src/commands/github.ts | 42 +++--- packages/cli/src/commands/init.ts | 10 +- packages/cli/src/hooks/maxsim-statusline.ts | 12 -- packages/cli/src/install/sync.ts | 128 ++++++++++++++++++ packages/cli/tests/unit/statusline.test.ts | 28 ++-- .../tests/unit/workflow-validation.test.ts | 77 +++++++++++ templates/workflows/debug-loop.md | 6 +- templates/workflows/execute.md | 9 +- templates/workflows/fix-loop.md | 2 +- templates/workflows/go.md | 8 +- templates/workflows/health.md | 2 +- templates/workflows/improve.md | 4 +- templates/workflows/init-existing.md | 27 ++-- templates/workflows/new-milestone.md | 4 +- templates/workflows/new-project.md | 29 ++-- templates/workflows/security-audit.md | 4 +- templates/workflows/verify-phase.md | 4 +- 18 files changed, 327 insertions(+), 113 deletions(-) create mode 100644 packages/cli/src/install/sync.ts diff --git a/packages/cli/src/commands/config.ts b/packages/cli/src/commands/config.ts index ad59a52e..37f107f8 100644 --- a/packages/cli/src/commands/config.ts +++ b/packages/cli/src/commands/config.ts @@ -3,6 +3,8 @@ * Extracted from cli.ts to enable modular async dispatch. */ +import * as fs from 'node:fs'; +import * as path from 'node:path'; import { loadConfig, saveConfig, cmdOk, cmdErr } from '../core/index.js'; import { getPositionalArg, type CommandRegistry } from './types.js'; @@ -61,6 +63,48 @@ export const CONFIG_COMMANDS: CommandRegistry = { }, }, + 'config-save-defaults': { + name: 'config-save-defaults', + description: 'Save current config as a defaults snapshot. Usage: config-save-defaults ', + async handler(args) { + const dest = getPositionalArg(args, 0); + if (!dest) { + return cmdErr('Usage: config-save-defaults '); + } + const config = loadConfig(process.cwd()); + const destPath = path.resolve(dest); + fs.mkdirSync(path.dirname(destPath), { recursive: true }); + fs.writeFileSync(destPath, JSON.stringify(config, null, 2), 'utf8'); + return cmdOk(`Defaults saved to ${destPath}`); + }, + }, + + 'validate-structure': { + name: 'validate-structure', + description: 'Validate the MaxsimCLI directory structure. Usage: validate-structure', + async handler(_args) { + const projectDir = process.cwd(); + const checks = [ + { path: '.claude', label: '.claude/ directory' }, + { path: path.join('.claude', 'maxsim'), label: '.claude/maxsim/ directory' }, + { path: path.join('.claude', 'maxsim', 'bin', 'maxsim-tools.cjs'), label: '.claude/maxsim/bin/maxsim-tools.cjs' }, + { path: path.join('.claude', 'maxsim', 'config.json'), label: '.claude/maxsim/config.json' }, + ]; + + const results: string[] = []; + let allPassed = true; + for (const check of checks) { + const fullPath = path.join(projectDir, check.path); + const exists = fs.existsSync(fullPath); + results.push(`${exists ? 'PASS' : 'FAIL'}: ${check.label}`); + if (!exists) allPassed = false; + } + + const summary = allPassed ? 'All checks passed.' : 'Some checks failed.'; + return cmdOk(`${results.join('\n')}\n${summary}`); + }, + }, + 'config-ensure-section': { name: 'config-ensure-section', description: 'Create a top-level config section if it does not already exist.', diff --git a/packages/cli/src/commands/github.ts b/packages/cli/src/commands/github.ts index 30ebea65..55d37060 100644 --- a/packages/cli/src/commands/github.ts +++ b/packages/cli/src/commands/github.ts @@ -386,7 +386,7 @@ export const GITHUB_COMMANDS: CommandRegistry = { 'post-comment': { name: 'post-comment', - description: 'Post a comment on an issue. Usage: post-comment --issue-number 216 --body "text" [--body-file /path] [--type plan]', + description: 'Post a comment on an issue. Usage: post-comment --issue-number 216 --body "text" [--body-file /path] [--type plan] [--plan-number 1]', async handler(args) { let issueNumber: number; try { @@ -413,8 +413,13 @@ export const GITHUB_COMMANDS: CommandRegistry = { } const commentType = getFlag(args, '--type'); + const planNumber = getIntFlag(args, '--plan-number'); if (commentType) { - const header = formatCommentHeader({ type: commentType as Parameters[0]['type'] }); + const meta: Record = { type: commentType }; + if (planNumber !== undefined && !Number.isNaN(planNumber)) { + meta.plan = planNumber; + } + const header = formatCommentHeader(meta as Parameters[0]); body = `${header}\n${body}`; } @@ -573,7 +578,7 @@ export const GITHUB_COMMANDS: CommandRegistry = { 'delete-comments': { name: 'delete-comments', - description: 'Delete comments of a given type from an issue. Usage: delete-comments --issue-number 216 --type plan', + description: 'Delete comments of a given type from an issue. Supports comma-separated types. Usage: delete-comments --issue-number 216 --type plan,context,research', async handler(args) { let issueNumber: number; try { @@ -584,26 +589,30 @@ export const GITHUB_COMMANDS: CommandRegistry = { return cmdErr((e as Error).message); } - let type: string; + let typeRaw: string; try { - type = getRequiredFlag(args, '--type'); + typeRaw = getRequiredFlag(args, '--type'); } catch (e) { return cmdErr((e as Error).message); } + // Support comma-separated types (e.g. "plan,context,research") + const types = new Set(typeRaw.split(',').map((t) => t.trim()).filter(Boolean)); + const commentsResult = await listComments(issueNumber); if (!commentsResult.ok) return cmdErr(commentsResult.error); - const matching = commentsResult.data.filter( - (c) => parseCommentMeta(c.body)?.type === type, - ); + const matching = commentsResult.data.filter((c) => { + const meta = parseCommentMeta(c.body); + return meta ? types.has(meta.type) : false; + }); for (const comment of matching) { const deleteResult = await deleteComment(comment.id); if (!deleteResult.ok) return cmdErr(deleteResult.error); } - return cmdOk(`Deleted ${matching.length} ${type} comments from #${issueNumber}`); + return cmdOk(`Deleted ${matching.length} [${[...types].join(',')}] comments from #${issueNumber}`); }, }, @@ -684,15 +693,6 @@ export const GITHUB_COMMANDS: CommandRegistry = { }, }, - 'set-status': { - name: 'set-status', - description: 'Set an issue status on the project board (alias for move-issue). Usage: set-status --issue-number 216 --status "Done"', - async handler(args) { - // Delegate to move-issue handler - return GITHUB_COMMANDS['move-issue'].handler(args); - }, - }, - // ── Task 3.1: GitHub status diagnostic ────────────────────────────── 'status': { @@ -1041,13 +1041,13 @@ export const GITHUB_COMMANDS: CommandRegistry = { name: 'all-progress', description: 'Show adaptive progress for all phase issues with detail levels and progress bar. Usage: all-progress', async handler(_args) { - // Fetch all phase issues (label is 'maxsim:phase' per actual GitHub data) - const result = await listIssues({ labels: 'maxsim:phase', state: 'all' }); + // Fetch all phase issues by canonical label (type:phase per types.ts) + const result = await listIssues({ labels: 'type:phase', state: 'all' }); if (!result.ok) return cmdErr(result.error); const phases = result.data; if (phases.length === 0) { - return cmdOk('No phase issues found (label: maxsim:phase)'); + return cmdOk('No phase issues found (label: type:phase)'); } // Extract phase number from title (e.g., "Phase 2: ..." → 2) for sorting diff --git a/packages/cli/src/commands/init.ts b/packages/cli/src/commands/init.ts index b305f011..d1aa3bff 100644 --- a/packages/cli/src/commands/init.ts +++ b/packages/cli/src/commands/init.ts @@ -78,10 +78,10 @@ export const INIT_COMMANDS: CommandRegistry = { const researcherModel = resolveModel(profile, AgentType.RESEARCHER, overrides); // Find the phase issue - const issuesResult = await listIssues({ labels: 'maxsim:phase', state: 'open' }); + const issuesResult = await listIssues({ labels: 'type:phase', state: 'open' }); if (!issuesResult.ok) { // Try all states if open search fails - const allIssuesResult = await listIssues({ labels: 'maxsim:phase', state: 'all' }); + const allIssuesResult = await listIssues({ labels: 'type:phase', state: 'all' }); if (!allIssuesResult.ok) { process.stdout.write(JSON.stringify({ phase_found: false, phase_number: phaseNumber })); return cmdOk(null); @@ -110,7 +110,7 @@ export const INIT_COMMANDS: CommandRegistry = { // Retry with all states if not found among open issues if (!phaseIssue) { - const allIssuesResult = await listIssues({ labels: 'maxsim:phase', state: 'all' }); + const allIssuesResult = await listIssues({ labels: 'type:phase', state: 'all' }); if (allIssuesResult.ok) { phaseIssue = allIssuesResult.data.find((i) => { const m = i.title.match(/^Phase\s+(\d+)/i); @@ -288,7 +288,7 @@ export const INIT_COMMANDS: CommandRegistry = { /** Find a phase issue by number, searching open issues first then all. */ async function findPhaseIssue(phaseNumber: number) { - const openResult = await listIssues({ labels: 'maxsim:phase', state: 'open' }); + const openResult = await listIssues({ labels: 'type:phase', state: 'open' }); if (openResult.ok) { const found = openResult.data.find((i) => { const m = i.title.match(/^Phase\s+(\d+)/i); @@ -297,7 +297,7 @@ async function findPhaseIssue(phaseNumber: number) { if (found) return found; } - const allResult = await listIssues({ labels: 'maxsim:phase', state: 'all' }); + const allResult = await listIssues({ labels: 'type:phase', state: 'all' }); if (!allResult.ok) return null; return allResult.data.find((i) => { diff --git a/packages/cli/src/hooks/maxsim-statusline.ts b/packages/cli/src/hooks/maxsim-statusline.ts index d50423a4..a4e656eb 100644 --- a/packages/cli/src/hooks/maxsim-statusline.ts +++ b/packages/cli/src/hooks/maxsim-statusline.ts @@ -41,16 +41,6 @@ function readMaxsimConfig(projectDir: string): MaxsimConfig | null { } } -/** Check whether any maxsim phase plan files exist. */ -function hasPhaseFiles(projectDir: string): boolean { - const phasesDir = path.join(projectDir, CLAUDE_DIR, 'maxsim', 'phases'); - try { - return fs.existsSync(phasesDir) && fs.readdirSync(phasesDir).length > 0; - } catch { - return false; - } -} - readStdinJson((input) => { const projectDir = resolveProjectDir(input); const config = readMaxsimConfig(projectDir); @@ -66,8 +56,6 @@ readStdinJson((input) => { } else { statusText = `MAXSIM \u25ba ${status}`; } - } else if (hasPhaseFiles(projectDir)) { - statusText = 'MAXSIM \u25ba In Progress'; } else { statusText = 'MAXSIM \u25ba Ready'; } diff --git a/packages/cli/src/install/sync.ts b/packages/cli/src/install/sync.ts new file mode 100644 index 00000000..8784d768 --- /dev/null +++ b/packages/cli/src/install/sync.ts @@ -0,0 +1,128 @@ +/** + * Template sync — one-way sync from bundled templates to .claude/. + * + * Compares file hashes to avoid unnecessary writes. Skips user-modified + * files like config.json. Used for updating an existing installation + * without a full reinstall. + */ + +import * as fs from 'node:fs'; +import * as path from 'node:path'; +import * as crypto from 'node:crypto'; +import { getTemplatesDir, isTextTemplate, processTemplate } from './copy.js'; + +/** Files that should never be overwritten by sync (user-modified). */ +const EXCLUDED_FILES = new Set([ + 'config.json', +]); + +/** Directories relative to the dest root that should be skipped entirely. */ +const EXCLUDED_DIRS = new Set([ + 'bin', +]); + +export interface SyncResult { + copied: string[]; + skipped: string[]; + unchanged: string[]; +} + +/** Compute SHA-256 hash of file content. */ +function fileHash(filePath: string): string { + const content = fs.readFileSync(filePath); + return crypto.createHash('sha256').update(content).digest('hex'); +} + +/** + * Recursively sync files from `src` to `dest`, only writing when content + * differs. Returns lists of copied, skipped, and unchanged files. + */ +function syncDir( + src: string, + dest: string, + templateVars: Record | undefined, + result: SyncResult, + relativeBase = '', +): void { + if (!fs.existsSync(src)) return; + + fs.mkdirSync(dest, { recursive: true }); + + for (const entry of fs.readdirSync(src, { withFileTypes: true })) { + const srcPath = path.join(src, entry.name); + const destPath = path.join(dest, entry.name); + const relativePath = path.join(relativeBase, entry.name); + + if (entry.isDirectory()) { + if (EXCLUDED_DIRS.has(entry.name)) { + result.skipped.push(relativePath); + continue; + } + syncDir(srcPath, destPath, templateVars, result, relativePath); + continue; + } + + // Skip excluded files + if (EXCLUDED_FILES.has(entry.name)) { + result.skipped.push(relativePath); + continue; + } + + // Determine source content (with template substitution for text files) + let srcContent: Buffer; + if (templateVars && isTextTemplate(srcPath)) { + const text = fs.readFileSync(srcPath, 'utf8'); + srcContent = Buffer.from(processTemplate(text, templateVars), 'utf8'); + } else { + srcContent = fs.readFileSync(srcPath); + } + + // Compare with existing destination + if (fs.existsSync(destPath)) { + const destHash = fileHash(destPath); + const srcHash = crypto.createHash('sha256').update(srcContent).digest('hex'); + if (srcHash === destHash) { + result.unchanged.push(relativePath); + continue; + } + } + + // Write the updated file + fs.mkdirSync(path.dirname(destPath), { recursive: true }); + fs.writeFileSync(destPath, srcContent); + result.copied.push(relativePath); + } +} + +/** + * Sync bundled templates to the project's .claude/ directory. + * + * One-way sync: templates → .claude/. Files that haven't changed + * (by SHA-256 hash) are not touched. Excluded files (config.json, + * bin/) are always skipped. + */ +export function syncTemplates( + projectDir: string, + templateVars?: Record, +): SyncResult { + const templatesDir = getTemplatesDir(); + const claudeDir = path.join(projectDir, '.claude'); + + const result: SyncResult = { copied: [], skipped: [], unchanged: [] }; + + const syncMappings = [ + { src: path.join(templatesDir, 'commands'), dest: path.join(claudeDir, 'commands') }, + { src: path.join(templatesDir, 'agents'), dest: path.join(claudeDir, 'agents') }, + { src: path.join(templatesDir, 'skills'), dest: path.join(claudeDir, 'skills') }, + { src: path.join(templatesDir, 'rules'), dest: path.join(claudeDir, 'rules') }, + { src: path.join(templatesDir, 'workflows'), dest: path.join(claudeDir, 'maxsim', 'workflows') }, + { src: path.join(templatesDir, 'references'), dest: path.join(claudeDir, 'maxsim', 'references') }, + { src: path.join(templatesDir, 'templates'), dest: path.join(claudeDir, 'maxsim', 'templates') }, + ]; + + for (const { src, dest } of syncMappings) { + syncDir(src, dest, templateVars, result); + } + + return result; +} diff --git a/packages/cli/tests/unit/statusline.test.ts b/packages/cli/tests/unit/statusline.test.ts index 9633f2f4..db2b57dc 100644 --- a/packages/cli/tests/unit/statusline.test.ts +++ b/packages/cli/tests/unit/statusline.test.ts @@ -67,18 +67,6 @@ function writeConfig(config: Record): void { ); } -/** Create phase files in the temp project directory. */ -function createPhaseFiles(count: number): void { - const phasesDir = path.join(tmpDir, '.claude', 'maxsim', 'phases'); - fs.mkdirSync(phasesDir, { recursive: true }); - for (let i = 1; i <= count; i++) { - fs.writeFileSync( - path.join(phasesDir, `phase-${i}.json`), - JSON.stringify({ phase: i }), - 'utf8', - ); - } -} /** Get the status text that was written to stdout. */ function getStatusOutput(): string { @@ -167,19 +155,23 @@ describe('output when config has no currentPhase', () => { }); // --------------------------------------------------------------------------- -// Output when no config exists but phase files exist +// Output when no config exists (phase files are irrelevant — config-only) // --------------------------------------------------------------------------- describe('output when no config exists but phase files exist', () => { - it('outputs "MAXSIM > In Progress" when phase files exist but no config', async () => { - createPhaseFiles(3); + it('outputs "MAXSIM > Ready" when phase files exist but no config (config-only approach)', async () => { + // Phase files in .claude/maxsim/phases/ are not consulted — only config matters + const phasesDir = path.join(tmpDir, '.claude', 'maxsim', 'phases'); + fs.mkdirSync(phasesDir, { recursive: true }); + fs.writeFileSync(path.join(phasesDir, 'phase-1.json'), '{}', 'utf8'); + await loadHook(); hookCallback!({ cwd: tmpDir }); const output = getStatusOutput(); expect(output).toContain('MAXSIM'); - expect(output).toContain('In Progress'); + expect(output).toContain('Ready'); }); }); @@ -237,7 +229,9 @@ describe('exit code', () => { }); it('exits 0 when phase files exist but no config', async () => { - createPhaseFiles(1); + const phasesDir = path.join(tmpDir, '.claude', 'maxsim', 'phases'); + fs.mkdirSync(phasesDir, { recursive: true }); + fs.writeFileSync(path.join(phasesDir, 'phase-1.json'), '{}', 'utf8'); await loadHook(); hookCallback!({ cwd: tmpDir }); diff --git a/packages/cli/tests/unit/workflow-validation.test.ts b/packages/cli/tests/unit/workflow-validation.test.ts index 716aaf12..8b4f267d 100644 --- a/packages/cli/tests/unit/workflow-validation.test.ts +++ b/packages/cli/tests/unit/workflow-validation.test.ts @@ -92,6 +92,83 @@ describe('workflow validation', () => { }); }); + describe('CLI command references in workflows', () => { + // Regex to match CLI invocations: node ... maxsim-tools.cjs [subcommand] + // Requires 'node' before maxsim-tools.cjs to avoid matching prose like "maxsim-tools.cjs present" + const CLI_INVOCATION_REGEX = /node\s+\S*maxsim-tools\.cjs\s+([\w-]+)(?:\s+([\w-]+))?/g; + + // Known CLI commands (flat) + const FLAT_COMMANDS = new Set([ + 'resolve-model', 'resolve-max-agents', 'resolve-wave-size', + 'config-get', 'config-set', 'config-ensure-section', + 'config-save-defaults', 'validate-structure', + ]); + + // Known namespaced commands + const NAMESPACE_COMMANDS: Record> = { + github: new Set([ + 'get-issue', 'list-issues', 'list-sub-issues', + 'post-comment', 'close-issue', 'reopen-issue', 'create-issue', + 'ensure-labels', 'add-label', 'remove-label', 'delete-comments', + 'move-issue', 'set-project', 'status', 'create-phase', 'create-milestone', + 'post-plan-comment', 'batch-create-tasks', 'all-progress', + 'detect-external-edits', 'handle-verification-failure', 'handle-verification-success', + ]), + init: new Set([ + 'plan-phase', 'execute-phase', 'phase-op', + ]), + }; + + it('all CLI command references in workflow files point to real commands', () => { + const errors: string[] = []; + + for (const file of workflowFiles) { + const content = fs.readFileSync(path.join(workflowsDir, file), 'utf-8'); + let match; + const regex = new RegExp(CLI_INVOCATION_REGEX.source, 'g'); + + while ((match = regex.exec(content)) !== null) { + const first = match[1]; + const second = match[2]; + + if (NAMESPACE_COMMANDS[first]) { + // Namespace command: github , init + if (second && !NAMESPACE_COMMANDS[first].has(second)) { + errors.push(`${file}: unknown ${first} subcommand "${second}"`); + } + } else if (!FLAT_COMMANDS.has(first)) { + // Not a known namespace and not a known flat command + errors.push(`${file}: unknown command "${first}${second ? ' ' + second : ''}"`); + } + } + } + + expect(errors, `Broken CLI references:\n${errors.join('\n')}`).toHaveLength(0); + }); + }); + + describe('workflow file references', () => { + it('all .claude/maxsim/workflows/ references point to existing workflow files', () => { + const errors: string[] = []; + const WORKFLOW_REF_REGEX = /\.claude\/maxsim\/workflows\/([\w-]+\.md)/g; + + for (const file of workflowFiles) { + const content = fs.readFileSync(path.join(workflowsDir, file), 'utf-8'); + let match; + const regex = new RegExp(WORKFLOW_REF_REGEX.source, 'g'); + + while ((match = regex.exec(content)) !== null) { + const referenced = match[1]; + if (!workflowFiles.includes(referenced)) { + errors.push(`${file}: references non-existent workflow "${referenced}"`); + } + } + } + + expect(errors, `Broken workflow references:\n${errors.join('\n')}`).toHaveLength(0); + }); + }); + describe('LS, TodoRead, TodoWrite in Plan Mode commands', () => { const planModeCommands = [ 'go.md', diff --git a/templates/workflows/debug-loop.md b/templates/workflows/debug-loop.md index 433901a8..1a02a31b 100644 --- a/templates/workflows/debug-loop.md +++ b/templates/workflows/debug-loop.md @@ -56,7 +56,7 @@ cat > "$TMPFILE" << 'BODY_EOF' $REPRO_OUTPUT ``` BODY_EOF -gh issue create \ +node .claude/maxsim/bin/maxsim-tools.cjs github create-issue \ --title "debug: $SYMPTOM" \ --label "debug" \ --body-file "$TMPFILE" @@ -91,7 +91,7 @@ Confirm to begin? (yes / edit / cancel) **Handle user response:** - **If user approves:** proceed to step 6 - **If user requests changes:** revise the relevant parameters (scope, reproduction command, investigation approach). If the reproduction command changed, re-attempt reproduction (step 3). Update the GitHub Issue body with the revised plan. Return to step 5 (stay in Plan Mode). -- **If user cancels:** close the GitHub Issue (`gh issue close $ISSUE_NUM --comment "Debug session cancelled by user before investigation began"`), Exit Plan Mode via `ExitPlanMode`, and stop. +- **If user cancels:** close the GitHub Issue (`node .claude/maxsim/bin/maxsim-tools.cjs github close-issue --issue-number $ISSUE_NUM`), Exit Plan Mode via `ExitPlanMode`, and stop. ## Step 6: Exit Plan Mode @@ -314,7 +314,7 @@ BODY_EOF node .claude/maxsim/bin/maxsim-tools.cjs github post-comment \ --issue-number $ISSUE_NUM --body-file "$TMPFILE" --type summary -gh issue close $ISSUE_NUM --comment "Debug session complete. Root cause: {found/not found}." +node .claude/maxsim/bin/maxsim-tools.cjs github close-issue --issue-number $ISSUE_NUM ``` diff --git a/templates/workflows/execute.md b/templates/workflows/execute.md index f2788a42..0fa9f70f 100644 --- a/templates/workflows/execute.md +++ b/templates/workflows/execute.md @@ -370,7 +370,7 @@ If any spot-check fails: report which plan failed. Ask user: "Retry plan or cont For each plan in the completed wave, iterate over its task sub-issues: ```bash -gh issue list --repo {owner}/{repo} --label "type:task" --json number,title,state,body -q '.[] | select(.number == TASK_ID)' +node .claude/maxsim/bin/maxsim-tools.cjs github get-issue --issue-number TASK_ID ``` For each task sub-issue: @@ -549,7 +549,10 @@ node .claude/maxsim/bin/maxsim-tools.cjs github post-comment \ Mark phase complete: ```bash -node .claude/maxsim/bin/maxsim-tools.cjs phase complete "${PHASE_NUMBER}" +node .claude/maxsim/bin/maxsim-tools.cjs github move-issue \ + --issue-number $PHASE_ISSUE_NUMBER --status "Done" +node .claude/maxsim/bin/maxsim-tools.cjs github close-issue \ + --issue-number $PHASE_ISSUE_NUMBER ``` > **Push strategy:** A single push occurs after full phase verification rather than per-wave, to avoid pushing partially-verified work. Each wave's merges are committed locally and verified by the test suite (step 6.8) before the next wave begins. If execution is interrupted, local commits preserve progress for re-entry via `/maxsim:execute`. @@ -627,7 +630,7 @@ cat > "$TMPFILE" << 'BODY_EOF' - Check for environmental dependencies - Consider breaking the task into smaller sub-tasks BODY_EOF -gh issue create \ +node .claude/maxsim/bin/maxsim-tools.cjs github create-issue \ --title "fix: [Phase {N}] Task {id} failed after 4 attempts" \ --body-file "$TMPFILE" \ --label "type:bug" --label "maxsim:auto" diff --git a/templates/workflows/fix-loop.md b/templates/workflows/fix-loop.md index 1c86b052..1a274c36 100644 --- a/templates/workflows/fix-loop.md +++ b/templates/workflows/fix-loop.md @@ -206,7 +206,7 @@ If the same error persists after 3 fix attempts with different approaches (`$ATT 4. **Escalate** — if still stuck, create a GitHub Issue for each resistant error: ```bash -gh issue create \ +node .claude/maxsim/bin/maxsim-tools.cjs github create-issue \ --title "fix-loop: resistant error in $FILE:$LINE" \ --label "type:bug" --label "maxsim:auto" \ --body "## Resistant Error\n\n**Error:** $ERROR_MESSAGE\n**File:** $FILE:$LINE\n\n## Approaches Tried\n{list of 3+ approaches attempted}\n\n## Context\nThis error persisted after $ATTEMPT_COUNT fix attempts during an autonomous fix-loop session." diff --git a/templates/workflows/go.md b/templates/workflows/go.md index 86f4f07f..fefa1c31 100644 --- a/templates/workflows/go.md +++ b/templates/workflows/go.md @@ -54,8 +54,8 @@ Returns: `phase_number`, `title`, `issue_number`, `total_tasks`, `completed_task **2. Open bugs and issues:** ```bash -gh issue list --label "type:bug" --state open --json number,title,createdAt -gh issue list --state open --json number,title,labels,createdAt +node .claude/maxsim/bin/maxsim-tools.cjs github list-issues --label "type:bug" --state open +node .claude/maxsim/bin/maxsim-tools.cjs github list-issues --state open ``` **3. Git context:** @@ -115,7 +115,7 @@ Wait for user response before continuing. Detection rule: If open issues labeled `type:bug` exist → propose `/maxsim:debug` to investigate and fix them. -If `gh issue list --label "type:bug"` returned open issues: +If `list-issues --label "type:bug"` returned open issues: ``` ## Problem Detected @@ -154,7 +154,7 @@ Impact: These issues were likely created directly on GitHub and are not in the p Resolution: Apply a `type:` label and `maxsim:user` to integrate them Options: -1. Triage now — for each issue, ask user for the type label and apply both `maxsim:user` and the chosen `type:` label via `gh issue edit {N} --add-label "maxsim:user,type:{chosen}"` +1. Triage now — for each issue, ask user for the type label and apply both `maxsim:user` and the chosen `type:` label via `node .claude/maxsim/bin/maxsim-tools.cjs github add-label --issue-number {N} --label "maxsim:user"` and `node .claude/maxsim/bin/maxsim-tools.cjs github add-label --issue-number {N} --label "type:{chosen}"` 2. View issues on GitHub 3. Skip and continue anyway ``` diff --git a/templates/workflows/health.md b/templates/workflows/health.md index 9f6d4a09..870b7bd0 100644 --- a/templates/workflows/health.md +++ b/templates/workflows/health.md @@ -9,7 +9,7 @@ Verify MaxsimCLI installation and GitHub connectivity. Report the status of each Verify the required files exist: ```bash -node .claude/maxsim/bin/maxsim-tools.cjs validate structure +node .claude/maxsim/bin/maxsim-tools.cjs validate-structure ``` Expected checks: diff --git a/templates/workflows/improve.md b/templates/workflows/improve.md index 10c75b39..b282d783 100644 --- a/templates/workflows/improve.md +++ b/templates/workflows/improve.md @@ -196,6 +196,8 @@ Continue to next iteration. ## Step 8: Stuck Detection +> **Reference:** See `.claude/maxsim/references/self-improvement.md` for detailed recovery strategies and anti-patterns. + After 5 consecutive discards (`$CONSECUTIVE_DISCARDS >= 5`): 1. **Full context reload** — re-read ALL in-scope files (complete refresh of understanding) @@ -206,7 +208,7 @@ After 5 consecutive discards (`$CONSECUTIVE_DISCARDS >= 5`): 6. **Escalation** — if still stuck after trying strategies 1-5, create a diagnostic GitHub Issue with all findings and escalate to the user: ```bash -gh issue create \ +node .claude/maxsim/bin/maxsim-tools.cjs github create-issue \ --title "improve: stuck after $CONSECUTIVE_DISCARDS consecutive failures" \ --label "type:bug" --label "maxsim:auto" \ --body "## Stuck Detection\n\nMetric: $METRIC_CMD\nBaseline: $BASELINE\nBest achieved: $BEST_METRIC\nConsecutive failures: $CONSECUTIVE_DISCARDS\n\n## Approaches Tried\n{summary from TSV log}\n\n## Suggested Investigation\n- Review scope constraints\n- Consider if metric is hitting a ceiling\n- Check for environmental factors" diff --git a/templates/workflows/init-existing.md b/templates/workflows/init-existing.md index efab9c5e..6b86d14b 100644 --- a/templates/workflows/init-existing.md +++ b/templates/workflows/init-existing.md @@ -93,7 +93,7 @@ Prompt: "Read .env.example, .env.sample, .env.template, or any documented enviro Prompt: "Look for Dockerfile, docker-compose.yml, Kubernetes manifests (k8s/, kubernetes/), Terraform (.tf files), AWS CDK, Pulumi, or serverless.yml. Identify: whether the app is containerized, the orchestration approach, cloud provider, and infrastructure-as-code tool. Return JSON with keys: containerized (boolean), base_image (string or null), orchestration (string or null), cloud_provider (string or null), iac_tool (string or null), deploy_notes (string)." **Agent 10 — Tech debt and open work:** -Prompt: "Search source files for TODO, FIXME, HACK, XXX, and DEPRECATED comments. Count occurrences per file. List the top 10 files by comment count. Also run: gh issue list --state open --json number,title,labels,createdAt to get open GitHub issues. Return JSON with keys: todo_files (array of {file, count}), total_todos (number), open_issues (array of {number, title, labels}), debt_notes (string)." +Prompt: "Search source files for TODO, FIXME, HACK, XXX, and DEPRECATED comments. Count occurrences per file. List the top 10 files by comment count. Also run: node .claude/maxsim/bin/maxsim-tools.cjs github list-issues --state open to get open GitHub issues. Return JSON with keys: todo_files (array of {file, count}), total_todos (number), open_issues (array of {number, title, labels}), debt_notes (string)." **Agent 11 — API surface (if applicable):** Prompt: "Look for route definitions, API endpoint declarations, OpenAPI/Swagger specs, GraphQL schemas, or gRPC proto files. Summarize the API surface: how many endpoints/operations, authentication mechanism, versioning strategy. Return JSON with keys: api_type (rest/graphql/grpc/none), endpoint_count (number or null), auth_mechanism (string or null), api_version (string or null), spec_file (string or null), api_notes (string)." @@ -160,17 +160,15 @@ Capture the project number. **4c. Store project board number:** ```bash -node .claude/maxsim/bin/maxsim-tools.cjs github set-project --number {PROJECT_NUMBER} +node .claude/maxsim/bin/maxsim-tools.cjs github set-project --project-number {PROJECT_NUMBER} ``` **4d. Create initial milestone:** ```bash -gh api repos/$REPO/milestones \ - --method POST \ - --field title="Milestone 1 — {project_name}" \ - --field description="Initial milestone created by MAXSIM" \ - --field state="open" +node .claude/maxsim/bin/maxsim-tools.cjs github create-milestone \ + --title "Milestone 1 — {project_name}" \ + --description "Initial milestone created by MAXSIM" ``` Capture the milestone number. @@ -208,13 +206,7 @@ Create `.claude/maxsim/config.json` with: **5b. Write or update CLAUDE.md:** -```bash -node .claude/maxsim/bin/maxsim-tools.cjs install write-claude-md \ - --project-name "{project_name}" \ - --description "{description}" -``` - -If unavailable, add the project context to the project root `CLAUDE.md` directly, or create a GitHub Wiki page for persistent reference. +Add the project context to the project root `CLAUDE.md` directly (CLAUDE.md generation is handled automatically by `npx maxsim` during installation). If CLAUDE.md already exists, append the MAXSIM context section. **5c. Commit initialization files:** @@ -269,13 +261,10 @@ Each phase issue must: 4. Be added to milestone #{MILESTONE_NUMBER} Commands: - gh issue create --title 'Phase N: {name}' --label 'phase:{N}' --milestone {MILESTONE_NUMBER} --body '{body}' --repo {owner/repo} - -After creating all issues, add each to the GitHub Project Board: - gh project item-add {PROJECT_NUMBER} --owner {OWNER} --url {issue_url} + node .claude/maxsim/bin/maxsim-tools.cjs github create-phase --phase-number N --title '{name}' --body '{body}' --milestone-number {MILESTONE_NUMBER} --project-number {PROJECT_NUMBER} Set each issue status to 'To Do': - node .claude/maxsim/bin/maxsim-tools.cjs github set-status --issue-number {N} --status 'To Do' + node .claude/maxsim/bin/maxsim-tools.cjs github move-issue --issue-number {N} --status 'To Do' Return the list of created issue numbers and titles." diff --git a/templates/workflows/new-milestone.md b/templates/workflows/new-milestone.md index 9eb611a5..c9f0befb 100644 --- a/templates/workflows/new-milestone.md +++ b/templates/workflows/new-milestone.md @@ -122,7 +122,7 @@ Track results. If any creation fails, warn and provide the manual creation comma After all phases are created, add each issue to the project board in the "To Do" column: ```bash -node .claude/maxsim/bin/maxsim-tools.cjs github set-status \ +node .claude/maxsim/bin/maxsim-tools.cjs github move-issue \ --issue-number [ISSUE_NUM] \ --status "To Do" ``` @@ -172,5 +172,5 @@ Start planning the first phase: - EnterPlanMode must be used before creating any GitHub resources - ExitPlanMode must be called after all GitHub resources are created - Use `node .claude/maxsim/bin/maxsim-tools.cjs` for CLI operations -- Use `github set-status` (not `github move-issue`) to set board column status +- Use `github move-issue` to set board column status diff --git a/templates/workflows/new-project.md b/templates/workflows/new-project.md index 0dfc323e..ee93ceae 100644 --- a/templates/workflows/new-project.md +++ b/templates/workflows/new-project.md @@ -97,7 +97,7 @@ Prompt: "Look for ORM configs, migration files, schema files, or database connec Prompt: "Look for Dockerfile, docker-compose.yml, Kubernetes manifests, Terraform, CDK, or serverless configs. Summarize: containerization approach, orchestration, cloud provider, infrastructure-as-code tool. Return JSON with keys: containerized, orchestration, cloud_provider, iac_tool." **Agent 10 — Open issues and tech debt:** -Prompt: "Search for TODO, FIXME, HACK, and DEPRECATED comments across source files. List the top 10 by frequency of occurrence. Also check if there are open GitHub issues via: gh issue list --state open --json number,title,labels. Return JSON with keys: todo_hotspots (array of {file, count}), open_issues (array of {number, title})." +Prompt: "Search for TODO, FIXME, HACK, and DEPRECATED comments across source files. List the top 10 by frequency of occurrence. Also check if there are open GitHub issues via: node .claude/maxsim/bin/maxsim-tools.cjs github list-issues --state open. Return JSON with keys: todo_hotspots (array of {file, count}), open_issues (array of {number, title})." After all agents complete, synthesize their JSON outputs into a single findings object. This feeds into the interview phase to pre-fill answers and skip redundant questions. @@ -162,17 +162,15 @@ Capture the project number from the output. **4c. Store project board number in config:** ```bash -node .claude/maxsim/bin/maxsim-tools.cjs github set-project --number {PROJECT_NUMBER} +node .claude/maxsim/bin/maxsim-tools.cjs github set-project --project-number {PROJECT_NUMBER} ``` **4d. Create initial milestone:** ```bash -gh api repos/$REPO/milestones \ - --method POST \ - --field title="Milestone 1 — {project_name}" \ - --field description="Initial milestone created by MAXSIM" \ - --field state="open" +node .claude/maxsim/bin/maxsim-tools.cjs github create-milestone \ + --title "Milestone 1 — {project_name}" \ + --description "Initial milestone created by MAXSIM" ``` ## Phase 5: Local Setup @@ -202,15 +200,7 @@ Create `.claude/maxsim/config.json` with: **5b. Write or update CLAUDE.md:** -Use the install system to write project-level CLAUDE.md context: - -```bash -node .claude/maxsim/bin/maxsim-tools.cjs install write-claude-md \ - --project-name "{project_name}" \ - --description "{description}" -``` - -If the command is unavailable, add the project context to the project root `CLAUDE.md` directly, or create a GitHub Wiki page for persistent reference. +Add the project context to the project root `CLAUDE.md` directly (CLAUDE.md generation is handled automatically by `npx maxsim` during installation). If CLAUDE.md already exists, append the MAXSIM context section. **5c. Commit initialization files:** @@ -250,13 +240,10 @@ Create 3–7 phase GitHub Issues on the repo {owner/repo}. Each phase issue shou 3. Have a body describing the phase goal and 5–10 acceptance criteria as a task list (- [ ] item) 4. Be added to milestone #{MILESTONE_NUMBER} -Use: gh issue create --title 'Phase N: {name}' --label 'phase:{N}' --milestone {MILESTONE_NUMBER} --body '{body}' - -After creating all issues, add each one to the GitHub Project Board: -gh project item-add {PROJECT_NUMBER} --owner {OWNER} --url {issue_url} +Use: node .claude/maxsim/bin/maxsim-tools.cjs github create-phase --phase-number N --title '{name}' --body '{body}' --milestone-number {MILESTONE_NUMBER} --project-number {PROJECT_NUMBER} Set each issue status to 'To Do' on the board using: -node .claude/maxsim/bin/maxsim-tools.cjs github set-status --issue-number {N} --status 'To Do' +node .claude/maxsim/bin/maxsim-tools.cjs github move-issue --issue-number {N} --status 'To Do' Return the list of created issue numbers and titles." diff --git a/templates/workflows/security-audit.md b/templates/workflows/security-audit.md index 32a97367..f9ac447a 100644 --- a/templates/workflows/security-audit.md +++ b/templates/workflows/security-audit.md @@ -363,7 +363,7 @@ Security controls that are correctly implemented: *This audit was performed by MaxsimCLI security scanner. It is not a substitute for professional penetration testing.* BODY_EOF -gh issue create \ +node .claude/maxsim/bin/maxsim-tools.cjs github create-issue \ --title "Security Audit: {date} — $AUDIT_SCOPE" \ --label "security-audit" \ --body-file "$TMPFILE" @@ -393,7 +393,7 @@ Top recommendations: 2. {second priority} 3. {third priority} -Full report: gh issue view $ISSUE_NUM +Full report: node .claude/maxsim/bin/maxsim-tools.cjs github get-issue --issue-number $ISSUE_NUM ``` diff --git a/templates/workflows/verify-phase.md b/templates/workflows/verify-phase.md index bf6fffb6..3822a732 100644 --- a/templates/workflows/verify-phase.md +++ b/templates/workflows/verify-phase.md @@ -10,6 +10,8 @@ Task completion does not equal goal achievement. This workflow checks the codeba Verify goal achievement, not task completion. A task "create chat component" can be marked done when the component is a placeholder. The task is done — the goal "working chat interface" is not. Evidence-first: every PASS or FAIL verdict must cite specific file paths, line numbers, or command output. + +> **Reference:** See `.claude/maxsim/references/verification-patterns.md` for verification methodology, common patterns, and anti-patterns. @@ -455,7 +457,7 @@ Record: PASS / FAIL + failing test names if failure. Before declaring phase verification complete, verify each individual task sub-issue: -1. List all task sub-issues: `gh issue list --repo {owner}/{repo} --search "parent:{phase_issue_number}" --json number,title,state` +1. List all task sub-issues: `node .claude/maxsim/bin/maxsim-tools.cjs github list-sub-issues --phase-issue-number {phase_issue_number}` 2. For each task: a. Confirm the sub-issue is **closed** b. Confirm at least one commit references the task number From d1f4d15c95b05fb006a7b6a1ce008837aaf14cfd Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 4 Apr 2026 13:47:07 +0000 Subject: [PATCH 2/3] fix(phase-3): fix lint errors in workflow validation test Use matchAll instead of while(regex.exec) to satisfy biome lint rules. https://claude.ai/code/session_01EMccZLcggSgkXGywamtcuk --- .../tests/unit/workflow-validation.test.ts | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/packages/cli/tests/unit/workflow-validation.test.ts b/packages/cli/tests/unit/workflow-validation.test.ts index 8b4f267d..27cdab3f 100644 --- a/packages/cli/tests/unit/workflow-validation.test.ts +++ b/packages/cli/tests/unit/workflow-validation.test.ts @@ -124,21 +124,19 @@ describe('workflow validation', () => { for (const file of workflowFiles) { const content = fs.readFileSync(path.join(workflowsDir, file), 'utf-8'); - let match; const regex = new RegExp(CLI_INVOCATION_REGEX.source, 'g'); - while ((match = regex.exec(content)) !== null) { - const first = match[1]; - const second = match[2]; + for (const m of content.matchAll(regex)) { + const first = m[1]; + const second = m[2]; if (NAMESPACE_COMMANDS[first]) { - // Namespace command: github , init if (second && !NAMESPACE_COMMANDS[first].has(second)) { errors.push(`${file}: unknown ${first} subcommand "${second}"`); } } else if (!FLAT_COMMANDS.has(first)) { - // Not a known namespace and not a known flat command - errors.push(`${file}: unknown command "${first}${second ? ' ' + second : ''}"`); + const label = second ? `${first} ${second}` : first; + errors.push(`${file}: unknown command "${label}"`); } } } @@ -150,15 +148,13 @@ describe('workflow validation', () => { describe('workflow file references', () => { it('all .claude/maxsim/workflows/ references point to existing workflow files', () => { const errors: string[] = []; - const WORKFLOW_REF_REGEX = /\.claude\/maxsim\/workflows\/([\w-]+\.md)/g; + const workflowRefRegex = /\.claude\/maxsim\/workflows\/([\w-]+\.md)/g; for (const file of workflowFiles) { const content = fs.readFileSync(path.join(workflowsDir, file), 'utf-8'); - let match; - const regex = new RegExp(WORKFLOW_REF_REGEX.source, 'g'); - while ((match = regex.exec(content)) !== null) { - const referenced = match[1]; + for (const m of content.matchAll(workflowRefRegex)) { + const referenced = m[1]; if (!workflowFiles.includes(referenced)) { errors.push(`${file}: references non-existent workflow "${referenced}"`); } From 658782d9df57ad578a006f98679374dddcc0563b Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 4 Apr 2026 13:48:01 +0000 Subject: [PATCH 3/3] chore: sync version to 5.23.0 from build Build-time version injection updated version.ts and config.json template. https://claude.ai/code/session_01EMccZLcggSgkXGywamtcuk --- package-lock.json | 28 +++++++++++++++++++++++++++- packages/cli/src/core/version.ts | 2 +- templates/templates/config.json | 2 +- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 009bf55a..72283f86 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9941,6 +9941,7 @@ "os": [ "aix" ], + "peer": true, "engines": { "node": ">=18" } @@ -9958,6 +9959,7 @@ "os": [ "android" ], + "peer": true, "engines": { "node": ">=18" } @@ -9975,6 +9977,7 @@ "os": [ "android" ], + "peer": true, "engines": { "node": ">=18" } @@ -9992,6 +9995,7 @@ "os": [ "android" ], + "peer": true, "engines": { "node": ">=18" } @@ -10009,6 +10013,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": ">=18" } @@ -10026,6 +10031,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": ">=18" } @@ -10043,6 +10049,7 @@ "os": [ "freebsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -10060,6 +10067,7 @@ "os": [ "freebsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -10077,6 +10085,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -10094,6 +10103,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -10111,6 +10121,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -10128,6 +10139,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -10145,6 +10157,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -10162,6 +10175,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -10179,6 +10193,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -10196,6 +10211,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -10213,6 +10229,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -10230,6 +10247,7 @@ "os": [ "netbsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -10247,6 +10265,7 @@ "os": [ "netbsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -10264,6 +10283,7 @@ "os": [ "openbsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -10281,6 +10301,7 @@ "os": [ "openbsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -10298,6 +10319,7 @@ "os": [ "openharmony" ], + "peer": true, "engines": { "node": ">=18" } @@ -10315,6 +10337,7 @@ "os": [ "sunos" ], + "peer": true, "engines": { "node": ">=18" } @@ -10332,6 +10355,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">=18" } @@ -10349,6 +10373,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">=18" } @@ -10366,6 +10391,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">=18" } @@ -10685,7 +10711,7 @@ }, "packages/cli": { "name": "maxsimcli", - "version": "5.18.0", + "version": "5.23.0", "license": "MIT", "dependencies": { "@octokit/plugin-retry": "^8.1.0", diff --git a/packages/cli/src/core/version.ts b/packages/cli/src/core/version.ts index c3619934..0430ed8d 100644 --- a/packages/cli/src/core/version.ts +++ b/packages/cli/src/core/version.ts @@ -1,5 +1,5 @@ /** MaxsimCLI version — auto-injected from package.json at build time. */ -export const VERSION = '5.22.0'; +export const VERSION = '5.23.0'; /** * Parse a semantic version string into components. diff --git a/templates/templates/config.json b/templates/templates/config.json index f75c9f3d..39e542a2 100644 --- a/templates/templates/config.json +++ b/templates/templates/config.json @@ -1,5 +1,5 @@ { - "version": "5.22.0", + "version": "5.23.0", "__doc_model_profiles": { "_description": "Reference: model assignments per profile. Actual values are defined in code — this table is for documentation only.", "quality": {