Release 0.30.0#1324
Conversation
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Ralph Khreish <35776126+Crunchyman-ralph@users.noreply.github.com>
…1181) Co-authored-by: Ralph Khreish <35776126+Crunchyman-ralph@users.noreply.github.com>
Co-authored-by: Ralph Khreish <35776126+Crunchyman-ralph@users.noreply.github.com>
- add search for brief selection
Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
🦋 Changeset detectedLatest commit: c1cd85e The changes in this PR will be included in the next version bump. Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
WalkthroughThis PR introduces an autonomous TDD workflow automation system with CLI commands ( Changes
Sequence Diagram(s)sequenceDiagram
participant AIAgent as AI Agent (Claude Code)
participant CLI as tm autopilot CLI
participant Orchestrator as WorkflowOrchestrator
participant Git as GitAdapter
participant Test as Test Runner
participant MCP as MCP Tools
AIAgent->>CLI: tm autopilot start taskId --max-attempts 3
CLI->>Orchestrator: initialize(taskId, subtasks)
Orchestrator->>Git: createBranch(feature/task-1)
Git-->>Orchestrator: branch created
Orchestrator-->>CLI: BRANCH_CREATED
CLI-->>AIAgent: workflow started
loop Per Subtask (RED → GREEN → COMMIT)
AIAgent->>MCP: autopilot_next()
MCP->>Orchestrator: getNextAction()
Orchestrator-->>MCP: RED phase, run tests
MCP-->>AIAgent: { action: 'run_tests', phase: 'RED' }
AIAgent->>Test: npm test
Test-->>AIAgent: tests failed (expected)
AIAgent->>MCP: autopilot_complete(--results '{...}')
MCP->>Orchestrator: completePhase(RED, results)
Orchestrator-->>MCP: GREEN phase ready
MCP-->>AIAgent: { nextPhase: 'GREEN', ... }
AIAgent->>Test: npm test
Test-->>AIAgent: tests passing
AIAgent->>MCP: autopilot_complete(--results '{...}')
MCP->>Orchestrator: completePhase(GREEN, results)
Orchestrator-->>MCP: COMMIT phase ready
AIAgent->>Git: git add & git commit
Git-->>AIAgent: committed
AIAgent->>MCP: autopilot_commit()
MCP->>Git: createCommit(message with metadata)
MCP->>Orchestrator: advancePhase(COMMIT)
Orchestrator-->>MCP: status after commit
MCP-->>AIAgent: subtask complete
end
AIAgent->>MCP: autopilot_status()
MCP->>Orchestrator: getStatus()
Orchestrator-->>MCP: all subtasks complete
MCP-->>AIAgent: { completed: true }
AIAgent->>MCP: autopilot_finalize()
MCP->>Orchestrator: finalizeWorkflow()
Orchestrator-->>MCP: workflow complete
MCP-->>AIAgent: workflow archived
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes This PR is substantial and intricate with multiple high-density subsystems:
The changes span diverse domains (CLI, MCP, Git ops, auth, config, docs), requiring verification across multiple interaction patterns, state management flows, error recovery, and integration points. Possibly related PRs
Suggested reviewers
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 49
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (11)
mcp-server/src/tools/move-task.js (1)
186-199: Critical issue: ReferenceError from undefined variables.Lines 186-199 reference
resultsandskippedMoveswhich are only defined inside the nestedif (fromIds.length > 1)block (lines 134-169). This code path executes whenfromIds.length !== toIds.lengthbutfromIds.length <= 1, causing a ReferenceError.Example:
from="5"(length=1) andto="6,7"(length=2) will trigger this bug.Additionally, there's an inconsistency: the inner block uses
skipped(line 176) but this code referencesskippedMoves(line 191).This appears to be duplicate/dead code that should be removed.
Apply this diff to remove the unreachable code block:
} else { // Moving a single task return handleApiResult(Or if this edge case (mismatched counts with single source ID) should be handled, add proper error handling:
+ return createErrorResponse( + 'Destination count must match source count, or provide a single destination for a single source', + 'MISMATCHED_ID_COUNTS' + ); } else {mcp-server/src/logger.js (1)
77-81: Comment and implementation are inconsistent.The comment on lines 77-80 states "Logs outside of tool execution (like startup) will go to stdout," but the code on line 81 unconditionally sends ALL logs to stderr using
console.error. This means startup logs will also go to stderr, contradicting the comment.Either:
- Update the comment to reflect that all logs now go to stderr, or
- Implement conditional logic to route logs based on execution context
- // Revert to console.log - FastMCP's context logger (context.log) - // is responsible for directing logs correctly (e.g., to stderr) - // during tool execution without upsetting the client connection. - // Logs outside of tool execution (like startup) will go to stdout. + // All logs are sent to stderr to avoid interfering with MCP protocol + // communication on stdout. FastMCP's context logger ensures proper + // log handling during tool execution. console.error(prefix, ...coloredArgs);mcp-server/src/tools/response-language.js (2)
13-14: Tool name should be snake_case.Rename
response-language→response_languageto match MCP tool naming convention.- name: 'response-language', + name: 'response_language',As per project tooling guidelines.
21-26: Make language optional to truly “Get or set”.Schema says “Get or set” but
languageis required. Mark it optional so clients can query current value without setting.- language: z - .string() + language: z + .string() + .optional() .describe( 'The new response language to set. like "中文" "English" or "español".' )docs/configuration.md (2)
270-299: MCP provider model IDs conflict with provider semantics.Under “MCP Provider > Available Model IDs”, listing Anthropic model IDs (e.g.,
claude-3-5-sonnet-20241022) is confusing ifprovider: "mcp"expectsmodelId: "mcp-sampling". Align examples so MCP usesmcp-sampling, and place Anthropic models under theanthropicprovider section.- - `mcp-sampling` - General text generation ... - - `claude-3-5-sonnet-20241022` - High-performance ... - - `claude-3-opus-20240229` - Enhanced reasoning ... + - `mcp-sampling` - General text generation via MCP client sampling (all roles) # Move Anthropic examples to an Anthropic provider section.Based on learnings.
503-651: The web query result confirms the package name and model identifiers align with official sources. To complete verification, I need to confirm the minimum version constraints (Node.js >= 18 and Codex CLI >= 0.42.0). Let me search for the specific version requirements:I found conflicting information on Node.js requirements. Let me search for the current official Codex CLI requirements to resolve this:
Based on my verification, I found critical information that needs updating:
Node.js requirement mismatch: Documentation states
>= 18, but the official package.json specifies>= 20.The official Codex CLI package.json specifies
"engines": { "node": ">=20" }, which conflicts with the documentation's claim ofNode.js >= 18. Additionally, practical guidance recommends Node.js version 22 or newer.Verification results on other claims:
- Latest version is 0.46.0
- Package name
@openai/codexis correct- GPT-5-Codex is the recommended model and gpt-5 and gpt-5-codex are both available
- ChatGPT account sign-in (OAuth) is the recommended authentication method
Update Node.js minimum version from
>= 18to>= 20in the prerequisites section (line 509).The documentation states "Node.js >= 18" but the official @openai/codex package.json enforces Node.js >= 20. This should be corrected before release to prevent installation failures on older Node.js versions.
packages/tm-core/src/config/services/config-persistence.service.ts (1)
48-55: HonormaxBackupsoption; currently ignored.
saveConfig()acceptsoptions.maxBackupsbutcreateBackup()always keeps 5. Thread the value through.async saveConfig( config: PartialConfiguration, options: PersistenceOptions = {} ): Promise<void> { - const { createBackup = false, atomic = true } = options; + const { createBackup = false, atomic = true, maxBackups = 5 } = options; try { if (createBackup && (await this.configExists())) { - await this.createBackup(); + await this.createBackup(maxBackups); } ... - private async createBackup(): Promise<string> { + private async createBackup(maxBackups = 5): Promise<string> { ... - await this.cleanOldBackups(); + await this.cleanOldBackups(maxBackups);Optional: include
backupDirandconfigPathin warn metadata for easier troubleshooting.Also applies to: 94-101, 107-123
packages/tm-core/src/auth/oauth-service.ts (1)
198-206: Avoid logging PII/secrets in CLI data (username, state).Debug log includes os.userInfo().username and the CSRF state (inside cliData). Mask or omit these fields in logs.
- this.logger.debug('CLI data:', cliData); + const { user, state, ...safeCliData } = cliData; + this.logger.debug('CLI data:', safeCliData);apps/cli/src/ui/components/header.component.ts (1)
61-79: Windows absolute-path check is incorrect.startsWith('/') fails on Windows drive-letter paths. Use path.isAbsolute.
-import chalk from 'chalk'; +import chalk from 'chalk'; +import path from 'node:path'; @@ - if (filePath) { - // Convert to absolute path if it's relative - const absolutePath = filePath.startsWith('/') - ? filePath - : `${process.cwd()}/${filePath}`; - console.log(`Listing tasks from: ${chalk.dim(absolutePath)}`); - } + if (filePath) { + const absolutePath = path.isAbsolute(filePath) + ? filePath + : path.join(process.cwd(), filePath); + console.log(`Listing tasks from: ${chalk.dim(absolutePath)}`); + }packages/tm-core/src/auth/credential-store.ts (1)
121-135: Corrupt-file cleanup won’t match current quarantine filenames.You write files like
.corrupt-<ts>-<pid>-<rand>but cleanup only matches digits, so files persist indefinitely. Fix the matcher.Apply:
-const suffix = file.slice(prefix.length); -if (!/^\d+$/.test(suffix)) continue; // Fixed regex, not from variable input +const suffix = file.slice(prefix.length); +// Support both ".corrupt-<ts>" and ".corrupt-<ts>-<pid>-<rand>" +const tsPart = suffix.split('-')[0]; +if (!/^\d+$/.test(tsPart)) continue;Optionally simplify quarantine name to just timestamp for future files:
-const corruptFile = `${this.config.configFile}.corrupt-${Date.now()}-${process.pid}-${Math.random().toString(36).slice(2, 8)}`; +const corruptFile = `${this.config.configFile}.corrupt-${Date.now()}`;Also applies to: 249-253
packages/tm-core/src/auth/auth-manager.ts (1)
156-167: Clear cached OrganizationService on logoutKeep invariant: after logout, no cached service. Avoid using a client with a revoked session.
Apply:
async logout(): Promise<void> { try { // First try to sign out from Supabase to revoke tokens await this.supabaseClient.signOut(); } catch (error) { // Log but don't throw - we still want to clear local credentials this.logger.warn('Failed to sign out from Supabase:', error); } // Always clear local credentials (removes auth.json file) this.credentialStore.clearCredentials(); + // Drop cached services + this.organizationService = undefined; }
🧹 Nitpick comments (99)
apps/mcp/tsconfig.json (1)
12-19: Redundant strict mode options;strict: truealready enables these.Line 12 sets
"strict": true, which automatically enables all the individual strict-mode sub-options on lines 13–19 (noImplicitAny, strictNullChecks, strictFunctionTypes, etc.). Listing them explicitly is redundant but harmless.Consider simplifying by removing lines 13–19, since they are already implied by
"strict": true:- "strict": true, - "noImplicitAny": true, - "strictNullChecks": true, - "strictFunctionTypes": true, - "strictBindCallApply": true, - "strictPropertyInitialization": true, - "noImplicitThis": true, - "alwaysStrict": true,.gitignore (1)
101-102: Clarify the purpose of the TaskMaster Workflow State comment.The comments document that TaskMaster Workflow State is stored globally and doesn't require a .gitignore entry. This is helpful context, but consider rephrasing for clarity. The current wording might confuse readers who expect an actual .gitignore entry to be present or removed.
Suggested revision:
-# TaskMaster Workflow State (now stored in ~/.taskmaster/sessions/) -# No longer needed in .gitignore as state is stored globally +# Note: TaskMaster Workflow State is stored globally at ~/.taskmaster/sessions/ +# and does not require a .gitignore entry in this repository.mcp-server/src/tools/research.js (1)
6-8: Improve the TODO comment for clarity and correct tracking.The
zod/v3import path is valid according to official Zod documentation, which states the package exposes versioned subpaths for backward compatibility. The runtime import error risk is resolved.However, improve the TODO comment for maintainability:
- Update the PR reference (currently references PR #1323, but this is PR #1324)
- Replace the PR reference with a specific GitHub issue number for tracking the reversion
- Clarify what "MCP spec issue" needs to be resolved (provide issue number or brief description)
Example:
TODO: Revert to 'zod' when issue #XXXX is resolved (zod-to-json-schema Draft-07 support).env.example (1)
17-17: Add trailing newline and consider key ordering.The static analysis tool suggests adding a blank line at the end of the file and reordering
GOOGLE_APPLICATION_CREDENTIALSbeforeVERTEX_LOCATIONfor consistency. These are minor formatting improvements.Apply this diff to address the linting hints:
VERTEX_LOCATION=us-central1 +GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account-credentials.json # Optional: Path to service account credentials JSON file (alternative to API key) -GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account-credentials.json +packages/tm-core/src/auth/supabase-session-storage.ts (1)
105-105: Consider consistent parameter usage for clarity.Line 105 now relies on the default
allowExpired: truebehavior, while line 81 (ingetItem) and lines 134 (inremoveItem) explicitly specify{ allowExpired: true }. While functionally equivalent based on the default behavior, this inconsistency may confuse future maintainers about the intent.Consider either:
- Being explicit everywhere for clarity, or
- Relying on defaults everywhere with a comment explaining the default behavior
Based on learnings.
- const existingCredentials = this.store.getCredentials(); + const existingCredentials = this.store.getCredentials({ allowExpired: true });.changeset/open-tips-notice.md (1)
5-5: Consider clarifying the model names.The changeset description is concise and user-facing. However, "4.5 haiku and sonnet" could be more explicit about the provider (e.g., "Claude 4.5 Haiku and Sonnet models") for readers unfamiliar with the naming convention.
-Add 4.5 haiku and sonnet to supported models for claude-code and anthropic ai providers +Add Claude 4.5 Haiku and Sonnet models to supported claude-code and anthropic AI providersmcp-server/src/tools/add-tag.js (1)
55-59: Sanitize logged arguments.Avoid logging full
args(may include large/sensitive fields). Log a whitelist.- log.info(`Starting add-tag with args: ${JSON.stringify(args)}`); + log.info( + `Starting add_tag with args: ${JSON.stringify({ + name: args.name, + copyFromCurrent: args.copyFromCurrent, + copyFromTag: args.copyFromTag, + fromBranch: args.fromBranch, + hasDescription: Boolean(args.description), + hasFile: Boolean(args.file) + })}` + );mcp-server/src/tools/response-language.js (2)
41-42: Pass projectRoot to handleApiResult for parity/telemetry.Other tools pass
args.projectRoot; do the same here for consistent telemetry/context.- return handleApiResult(result, log, 'Error setting response language'); + return handleApiResult( + result, + log, + 'Error setting response language', + undefined, + args.projectRoot + );
27-33: Sanitize logged args.Prefer a compact log over
JSON.stringify(args).- log.info( - `Executing response-language tool with args: ${JSON.stringify(args)}` - ); + log.info( + `Executing response_language with args: ${JSON.stringify({ + hasProjectRoot: Boolean(args.projectRoot), + languageSet: typeof args.language !== 'undefined' + })}` + );mcp-server/src/tools/update-subtask.js (2)
16-17: Avoid importing helpers from scripts path.
resolveTagshould live undermcp-server/src/core/utils(or be re-exported) to keep MCP server utilities self-contained.
Move/aliasresolveTaginto a core utils module and update the import here for consistency with the utilities guidance.
52-53: Don’t log full args; redact prompt/details.
promptmay contain sensitive/long content. Log IDs and flags only.- log.info(`Updating subtask with args: ${JSON.stringify(args)}`); + log.info( + `Updating subtask with args: ${JSON.stringify({ + id: args.id, + tag: args.tag ?? null, + research: Boolean(args.research), + hasFile: Boolean(args.file) + })}` + );mcp-server/src/tools/add-subtask.js (3)
49-57: Accept array for dependencies (keep string for backward compat).MCP clients send JSON; arrays are more natural than comma-separated strings. Support both.
- dependencies: z - .string() - .optional() - .describe('Comma-separated list of dependency IDs for the new subtask'), + dependencies: z + .union([z.string(), z.array(z.string())]) + .optional() + .describe( + 'Dependencies for the new subtask (array or comma-separated string)' + ),And normalize before calling the direct function:
- const result = await addSubtaskDirect({ + const deps = + Array.isArray(args.dependencies) + ? args.dependencies + : (args.dependencies?.split(',').map(s => s.trim()).filter(Boolean) ?? undefined); + const result = await addSubtaskDirect({ ... - dependencies: args.dependencies, + dependencies: deps,
16-17: Centralize resolveTag under MCP server utils.Same as update-subtask: avoid reaching into
scripts/modules.
73-75: Sanitize logged args.Don’t log entire
args(title/description/details may be verbose/sensitive).- log.info(`Adding subtask with args: ${JSON.stringify(args)}`); + log.info( + `Adding subtask with args: ${JSON.stringify({ + id: args.id, + hasTitle: Boolean(args.title), + hasDescription: Boolean(args.description), + hasDetails: Boolean(args.details), + status: args.status ?? 'pending', + hasDeps: Boolean(args.dependencies), + skipGenerate: Boolean(args.skipGenerate), + tag: args.tag ?? null, + hasFile: Boolean(args.file) + })}` + );.changeset/kind-lines-melt.md (1)
1-21: Condense the changeset to a single, user-facing summary line.Per repo guidelines, changeset entries should be brief and imperative. Move the detailed Problem/Solution notes to docs or the PR description, and keep a one‑liner here.
As per coding guidelines
--- "task-master-ai": patch --- - -Fix MCP server compatibility with Draft-07 clients (Augment IDE, gemini-cli, gemini code assist) - -- Resolves #1284 - -**Problem:** - -- MCP tools were using Zod v4, which outputs JSON Schema Draft 2020-12 -- MCP clients only support Draft-07 -- Tools were not discoverable in gemini-cli and other clients - -**Solution:** - -- Updated all MCP tools to import from `zod/v3` instead of `zod` -- Zod v3 schemas convert to Draft-07 via FastMCP's zod-to-json-schema -- Fixed logger to use stderr instead of stdout (MCP protocol requirement) - -This is a temporary workaround until FastMCP adds JSON Schema version configuration. +Fix Draft‑07 compatibility for MCP clients by switching MCP tools to zod/v3 and logging to stderr (resolves #1284).apps/mcp/src/tools/README-ZOD-V3.md (1)
36-36: Replace bare URL with a Markdown link (lint: MD034).- - Tracking: https://github.com/punkpeye/fastmcp/issues/189 + - Tracking: [punkpeye/fastmcp#189](https://github.com/punkpeye/fastmcp/issues/189)apps/docs/capabilities/mcp.mdx (2)
22-24: Avoid hard‑coding tool counts; they drift.Prefer wording that won’t require docs updates when tools change.
-#### All Tools (Default) -Loads all 36 available tools. Use when you need full Task Master functionality. +#### All Tools (Default) +Loads all available tools. Use when you need full Task Master functionality. -#### Core Tools (Lean Mode) -Loads only 7 essential tools for daily development. Ideal for minimal context usage. +#### Core Tools (Lean Mode) +Loads a minimal set of essential tools for daily development. Ideal for minimal context usage. -#### Standard Tools -Loads 15 commonly used tools. Balances functionality with context efficiency. +#### Standard Tools +Loads a commonly used set of tools. Balances functionality with context efficiency.Also applies to: 42-44, 72-73
69-70: Verified: "lean" aliases "core" and custom-list parsing is case‑insensitive with hyphen/underscore normalization.
- Implementation: registerTaskMasterTools (mcp-server/src/tools/index.js) maps 'lean' → core and normalizes input using toLowerCase plus underscore↔hyphen replacements.
- Tests: tests/unit/mcp/tools/tool-registration.test.js covers 'lean' and case‑insensitive inputs (e.g., 'CORE').
- Optional: add a short Zod v3 tooling note near the "uses zod" mention in apps/docs/capabilities/mcp.mdx.
.taskmaster/docs/tdd-workflow-phase-0-spike.md (1)
6-11: Link the implementation file and drop the stale effort estimate.
- Make the referenced command file a clickable relative link.
- Remove "Estimated Effort" now that the phase is complete to avoid stale data.
-See `apps/cli/src/commands/autopilot.command.ts` for implementation. +See [apps/cli/src/commands/autopilot.command.ts](../../apps/cli/src/commands/autopilot.command.ts) for implementation. -## Estimated Effort -2-3 days +<!-- Effort omitted post-completion to keep this doc timeless -->Also applies to: 120-122
apps/mcp/src/tools/autopilot/start.tool.ts (3)
18-37: Tighten input validation for safer defaults.
- taskId: ensure non‑empty.
- maxAttempts: integer ≥1 (and optionally cap to a sane upper bound).
- taskId: z - .string() + taskId: z + .string() + .min(1, 'taskId is required') .describe( 'Main task ID to start workflow for (e.g., "1", "2", "HAM-123"). Subtask IDs (e.g., "2.3", "1.1") are not allowed.' ), @@ - maxAttempts: z - .number() + maxAttempts: z + .number() + .int() + .min(1) + .max(10) .optional() .default(3)
14-16: Minor: consolidate imports from @tm/core.-import { createTaskMasterCore } from '@tm/core'; -import { WorkflowService } from '@tm/core'; +import { createTaskMasterCore, WorkflowService } from '@tm/core';
46-51: Minor: simpler main‑task check and trim.-function isMainTaskId(taskId: string): boolean { - // A main task has no dots in the ID after the optional prefix - // Examples: "1" ✓, "HAM-123" ✓, "1.1" ✗, "HAM-123.1" ✗ - const parts = taskId.split('.'); - return parts.length === 1; -} +function isMainTaskId(taskId: string): boolean { + const id = taskId.trim(); + return id.length > 0 && !id.includes('.'); +}packages/tm-core/src/auth/config.ts (1)
24-24: Non-null assertion on potentially undefined value.The non-null assertion operator (
BASE_DOMAIN!) asserts thatBASE_DOMAINwill never be undefined, but there's no runtime validation to guarantee this. If both environment variables are undefined or empty strings, this will result inbaseUrl: undefined!which equalsundefined, potentially causing subtle runtime errors.Consider adding runtime validation or maintaining a safe fallback value.
- baseUrl: BASE_DOMAIN!, + baseUrl: BASE_DOMAIN || 'https://tryhamster.com',Or add validation:
if (!BASE_DOMAIN) { throw new Error('BASE_DOMAIN must be configured via TM_BASE_DOMAIN or TM_PUBLIC_BASE_DOMAIN environment variable'); } export const DEFAULT_AUTH_CONFIG: AuthConfig = { baseUrl: BASE_DOMAIN, // ... };.taskmaster/docs/tdd-workflow-phase-3-extensibility-guardrails.md (5)
199-215: Add a language hint to this fenced block.Fix markdownlint MD040 by specifying a language, e.g., ```text.
110-135: Broaden framework detection.Include modern Python and JS signals and config files.
- if (await exists('pytest.ini') || await exists('setup.py')) { + if (await exists('pytest.ini') || await exists('tox.ini') || + await exists('pyproject.toml')) { return Framework.Pytest } @@ - if (await exists('package.json')) { + if (await exists('package.json')) { const pkg = await readJSON('package.json') if (pkg.devDependencies?.vitest) return Framework.Vitest if (pkg.devDependencies?.jest) return Framework.Jest + if (pkg.scripts?.test?.includes('vitest')) return Framework.Vitest + if (pkg.scripts?.test?.includes('jest')) return Framework.Jest + if (await exists('vitest.config.ts') || await exists('vitest.config.js')) return Framework.Vitest + if (await exists('jest.config.ts') || await exists('jest.config.js')) return Framework.Jest }
62-69: Align coverage parsing with commands.You’re parsing “pytest-cov XML,” but commands don’t emit it. Add explicit XML reports and file paths; likewise for Go.
-const output = await exec('pytest --cov --json-report') +const output = await exec('pytest --cov --cov-report=xml:coverage.xml --json-report')In parseCoverage, read coverage.xml (pytest) and coverage.out (Go).
Also applies to: 89-97
102-105: Clarify Go test file API naming.getTestFileExtension returns "_test.go" which is a suffix, not an extension. Rename to getTestFileSuffix() or return ".go" here to avoid confusion.
283-287: Normalize phase casing in state.Elsewhere phases use uppercase (e.g., FINALIZE/GREEN). Consider "currentPhase": "GREEN" for consistency.
apps/mcp/src/tools/autopilot/finalize.tool.ts (1)
81-96: Align nextAction payload shape with other tools.This tool returns nextAction and nextSteps; complete.tool also returns actionDescription. Standardize the fields across tools to reduce client branching.
apps/mcp/src/tools/autopilot/complete.tool.ts (2)
21-29: Validate internal consistency of test results.Add a refinement so total = passed + failed + skipped.
- testResults: z - .object({ + testResults: z + .object({ total: z.number().describe('Total number of tests'), passed: z.number().describe('Number of passing tests'), failed: z.number().describe('Number of failing tests'), skipped: z.number().optional().describe('Number of skipped tests') - }) + }) + .refine( + (t) => t.total === t.passed + t.failed + (t.skipped ?? 0), + 'total must equal passed + failed + skipped' + ) .describe('Test results from running the test suite')
121-134: Standardize response fields across tools.This tool returns actionDescription in addition to nextAction/nextSteps; finalize.tool omits it. Pick one consistent shape for all autopilot_* tools.
.taskmaster/docs/tdd-workflow-phase-1-orchestrator.md (1)
11-28: Fix fenced code block languages (MD040).Add a language to these fences to satisfy markdownlint and improve rendering.
-``` +```text ... -``` +```textAlso applies to: 154-166
apps/mcp/src/shared/utils.ts (1)
49-76: Propagate projectRoot to error responses for consistent tagging.handleApiResult supports projectRoot, but withNormalizedProjectRoot doesn’t pass it on errors; include it so clients get the tag in failures too.
- return handleApiResult({ + return handleApiResult({ result: { success: false, error: { message: 'Could not determine project root. Please provide projectRoot argument or ensure TASK_MASTER_PROJECT_ROOT environment variable is set.' } - } + }, + projectRoot: process.cwd() }); @@ - return handleApiResult({ + return handleApiResult({ result: { success: false, error: { message: `Operation failed: ${error.message}` } - } + }, + projectRoot: normalizedRoot ?? process.cwd() });Also applies to: 218-255
packages/tm-core/src/git/scope-detector.ts (1)
6-7: Remove stray TODO.The “// TODO: remove this” in header should be cleaned before release.
apps/cli/src/commands/autopilot.command.ts (1)
211-213: Ensure box width is an integer.boxen expects an integer width; passing a float can lead to odd wrapping. Cast it.
- width: process.stdout.columns ? process.stdout.columns * 0.95 : 100 + width: process.stdout.columns ? Math.floor(process.stdout.columns * 0.95) : 100mcp-server/src/tools/add-task.js (1)
93-93: Fix typo in comment.The comment contains a typo: "functionP" should be "function".
- // Call the direct functionP + // Call the direct functionCLAUDE.md (1)
36-39: Helpful documentation guidelines added.The new section clarifies where documentation should be written and referenced.
The static analysis tool flags the bare URL on line 39. Consider wrapping it in markdown link syntax:
-- **Documentation URL**: Reference docs at https://docs.task-master.dev, not local file paths +- **Documentation URL**: Reference docs at [docs.task-master.dev](https://docs.task-master.dev), not local file pathsapps/cli/tests/unit/commands/autopilot/shared.test.ts (1)
68-112: parseSubtasks mapping verified; consider one extra edge case.Tests assert status normalization and default
maxAttempts=3. Optionally add a case wheresubtasksexists but is empty to confirm it returns[].Based on learnings.
docs/configuration.md (2)
430-466: Azure section: small editorial and clarity tweaks.
- “Ensure youre not getting rate limited...” → “you’re” and clarify TPM guidance.
- Explicitly note deployment-name vs model-name (you did—good). Consider adding a one-line curl example for 401/404 triage.
- Ensure youre not getting rate limited by `maxTokens` maintain appropriate Tokens per Minute Rate Limit (TPM) in your deployment. + Ensure you’re within your Azure TPM limits; adjust request sizes (`maxTokens`) or apply for higher quota to avoid rate limiting.Also applies to: 471-478
132-156: Reinforce: keep non-secret settings out of .env.Good alignment with repo rules. Add one sentence that MCP stdio servers must set
TASK_MASTER_MCP=trueto suppress logs that can corrupt JSON-RPC.Based on learnings.
mcp-server/src/tools/README-ZOD-V3.md (2)
34-37: Replace bare URL with a proper Markdown link (lint).Format the tracking URL to satisfy MD034.
- Tracking: https://github.com/punkpeye/fastmcp/issues/189 + Tracking: <https://github.com/punkpeye/fastmcp/issues/189>
11-16: Add explicit zod version requirement to README-ZOD-V3.md and consider CI enforcement.Verification confirms
zod/v3is supported in your installedzod@^4.1.11(available since zod@3.25.0). All 36+ tool files consistently use the correct import pattern with zero plainzodimports found. However, the README lacks an explicit version requirement statement at the top (e.g., "Requires zod >= 3.25.0"), and no CI/linting checks were found to enforce the pattern. Update the README heading to include the version constraint and consider adding an eslint rule or CI check to prevent regressions.packages/tm-core/src/git/branch-name-generator.spec.ts (1)
35-66: Nice coverage; add one uniqueness/collision test.Consider asserting that two successive calls with same inputs yield distinct names if your generator appends a timestamp/nonce, or document determinism if not.
apps/cli/src/commands/auth.command.ts (2)
352-382: Use isDebugMode() instead of process.env.DEBUG for consistency.Replace direct
process.env.DEBUGchecks with the sharedisDebugMode()helper to keep behavior uniform.- if (process.env.DEBUG) { + import { isDebugMode } from '../utils/error-handler.js'; + if (isDebugMode()) { console.error(chalk.gray((contextError as Error).message)); }
172-239: displayStatus formatting/time math are fine; consider minutes modulo.Current code shows total minutes when <1h; optional: show “X minutes remaining” using
Math.ceiland clamp at ≥1 for imminent expiry.apps/mcp/package.json (1)
6-8: Nonstandard empty version; prefer removing or setting a valid SemVer.For a private package this is harmless but some tooling breaks on empty strings. Use “0.0.0” or drop the field.
- "version": "", + "version": "0.0.0"apps/cli/src/commands/show.command.ts (2)
11-15: Disambiguate displayError to avoid confusion with ui.displayError.Both error handlers exist (utils/error-handler and utils/ui). Rename the imported symbol for clarity and future-proofing.
-import { displayError } from '../utils/error-handler.js'; +import { displayError as displayException } from '../utils/error-handler.js';
116-118: Use the renamed error helper consistently (and consider not exiting inside catch).Apply the alias from the import and, if you need post-error cleanup, pass { skipExit: true } and handle exit after cleanup.
- displayError(error); + displayException(error);Optionally:
- displayException(error); + displayException(error, { skipExit: true }); + await this.cleanup(); + process.exit(1);Ensure this command’s lifecycle requires immediate exit; otherwise, prefer explicit cleanup-first exit as shown.
README.md (4)
115-134: Use jsonc in examples that include comments.These samples have // comments but the fence is JSON. Switch to jsonc to avoid copy-paste parse errors.
-```json +```jsonc { "mcpServers": { "task-master-ai": { "command": "npx", "args": ["-y", "task-master-ai"], "env": { - // "TASK_MASTER_TOOLS": "all", // Options: "all", "standard", "core", or comma-separated list of tools + // "TASK_MASTER_TOOLS": "all", // Options: "all", "standard", "core", or comma-separated list of tools "ANTHROPIC_API_KEY": "YOUR_ANTHROPIC_API_KEY_HERE",
145-167: Also switch this block to jsonc.Same comment style here; use jsonc for accuracy.
-```json +```jsonc { "servers": { "task-master-ai": {
287-356: Document base-domain env vars used for web URLs.display-helpers.ts reads TM_BASE_DOMAIN/TM_PUBLIC_BASE_DOMAIN to build links; add a brief note and example here so users know how header links are formed.
310-321: Unify npx args across samples.You mix ["-y","task-master-ai"] and ["-y","--package=task-master-ai","task-master-ai"]. Pick one pattern and use it consistently to reduce confusion.
apps/cli/src/ui/components/header.component.ts (2)
33-61: Clarify API header fallback when briefInfo is missing.If storageType='api' but briefInfo is absent, you render the tag (file-storage UX). Show a generic API source instead to avoid confusion.
- if (storageType === 'api' && briefInfo) { + if (storageType === 'api') { + if (briefInfo) { // existing brief rendering... - } else if (tag) { + } else { + console.log(`Listing tasks from: ${chalk.dim('API (no context)')}`); + return; + } + } else if (tag) {
41-49: Guard against double slashes in URLs.If webAppUrl ends with '/', the constructed URL contains '//home/...'. Either trim in helper (preferred) or here before interpolation.
apps/cli/src/utils/display-helpers.ts (2)
13-33: Normalize trailing slash in base domain.Trim trailing slashes to avoid ‘//home/...’ when composing links downstream.
function getWebAppUrl(): string | undefined { const baseDomain = process.env.TM_BASE_DOMAIN || process.env.TM_PUBLIC_BASE_DOMAIN; if (!baseDomain) { return undefined; } - // If it already includes protocol, use as-is - if (baseDomain.startsWith('http://') || baseDomain.startsWith('https://')) { - return baseDomain; + const sanitized = baseDomain.replace(/\/+$/, ''); + // If it already includes protocol, use as-is + if (sanitized.startsWith('http://') || sanitized.startsWith('https://')) { + return sanitized; } // Otherwise, add protocol based on domain - if (baseDomain.includes('localhost') || baseDomain.includes('127.0.0.1')) { - return `http://${baseDomain}`; + if (sanitized.includes('localhost') || sanitized.includes('127.0.0.1')) { + return `http://${sanitized}`; } - return `https://${baseDomain}`; + return `https://${sanitized}`; }
64-74: Consistent storageType handoff.If storageType='api' but no briefInfo, displayHeader currently falls back to tag UI. After adjusting header fallback, this is fine; otherwise consider forcing a generic API message here.
apps/cli/src/commands/autopilot/start.command.ts (2)
69-85: Ensure tmCore is closed on all paths.tmCore is only closed on success and a couple of error branches. Close in finally to prevent resource leaks in tests/CI.
-export class StartCommand extends Command { +export class StartCommand extends Command { @@ - private async execute(taskId: string, options: StartOptions): Promise<void> { + private async execute(taskId: string, options: StartOptions): Promise<void> { @@ - try { + // hoist for finally + let tmCore: Awaited<ReturnType<typeof createTaskMasterCore>> | undefined; + try { @@ - const tmCore = await createTaskMasterCore({ + tmCore = await createTaskMasterCore({ projectPath: mergedOptions.projectRoot! }); @@ - await tmCore.close(); } catch (error) { formatter.error((error as Error).message); if (mergedOptions.verbose) { console.error((error as Error).stack); } process.exit(1); - } + } finally { + try { await tmCore?.close(); } catch {} + }Also applies to: 118-166
106-116: Persist active tag in workflow metadata.Commit command reads state.context.metadata.tag. Set it here for consistent commit messages.
const context: WorkflowContext = { taskId: task.id, subtasks, currentSubtaskIndex: 0, errors: [], metadata: { startedAt: new Date().toISOString(), - tags: task.tags || [] + tags: task.tags || [], + tag: currentTag || 'master' } };apps/cli/src/utils/ui.ts (1)
135-141: getBoxWidth: consider stderr for error boxes.When piping stdout, process.stdout.columns may be undefined; for displayError you might prefer process.stderr.columns.
apps/mcp/src/tools/autopilot/abort.tool.ts (2)
17-21: Make projectRoot optional to leverage env/session resolution.withNormalizedProjectRoot already resolves projectRoot; requiring it in the schema forces callers to pass it.
-const AbortSchema = z.object({ - projectRoot: z - .string() - .describe('Absolute path to the project root directory') -}); +const AbortSchema = z.object({ + projectRoot: z + .string() + .describe('Absolute path to the project root directory') + .optional() +});
38-45: Minor: include hadWorkflow=false note in logs.You already return hadWorkflow; also log it at info for symmetry with success path.
apps/cli/src/utils/error-handler.ts (2)
56-59: Prefer setting exitCode over hard process.exit for safer teardownprocess.exit(1) can cut off logs/async cleanup. Set exitCode and return; let the caller decide if/when to exit.
- if (!options.skipExit) { - process.exit(1); - } + if (!options.skipExit) { + process.exitCode = 1; + }
14-16: Make DEBUG detection a bit more forgiving (optional)Support TRUE/On/Yes (case-insensitive) to match common conventions; keeps current behavior too.
-export function isDebugMode(): boolean { - return process.env.DEBUG === 'true' || process.env.DEBUG === '1'; -} +export function isDebugMode(): boolean { + const v = (process.env.DEBUG ?? '').trim().toLowerCase(); + return v === 'true' || v === '1' || v === 'on' || v === 'yes'; +}packages/tm-core/src/auth/credential-store.test.ts (1)
496-510: Intentional: expired credentials still count as “present”Matches the design where refresh is handled upstream (Supabase). Consider documenting this nuance in CredentialStore.hasCredentials JSDoc.
packages/tm-core/src/git/commit-message-generator.test.ts (3)
115-123: Rename for accuracy: it defaults to “repo” scope, not “omit scope”Align the title with behavior.
-it('should omit scope if not detected', () => { +it('should default to repo scope when none detected', () => {
22-30: Path consistency (optional)Repo uses apps/cli; the test references packages/cli. Adjust if scope detection relies on path segments.
- changedFiles: ['packages/cli/src/commands/start.ts'] + changedFiles: ['apps/cli/src/commands/start.ts']
202-219: Consider expanding valid types coverage (optional)If supported, include perf, build, ci, revert in valid types to harden validation.
- const validTypes = [ + const validTypes = [ 'feat', 'fix', 'docs', 'style', 'refactor', 'test', - 'chore' + 'chore', + 'perf', + 'build', + 'ci', + 'revert' ];.taskmaster/docs/autonomous-tdd-git-workflow.md (6)
297-313: Add language to fenced code block (markdownlint MD040)Use text for ASCII layout.
-``` +```text ┌─────────────────────────────────────┬──────────────────────────────────┐ ... └─────────────────────────────────────┴──────────────────────────────────┘--- `339-363`: **Add language to fenced code block (MD040)** This is plain text context; tag it. ```diff -``` +```text You are implementing: Task #42 [analytics]: User metrics tracking ...--- `429-435`: **Specify language for test output block (MD040) and avoid underscore emphasis (MD050)** Tag code fence and switch any _emphasis_ outside code blocks to **bold**. ```diff -``` +```text FAIL src/api/__tests__/metrics.test.js ...Additionally, search/replace single underscores used for emphasis outside code blocks with asterisks. --- `550-564`: **Add language to artifact structure block (MD040)** Tag as text. ```diff -``` +```text .taskmaster/reports/runs/2025-01-15-142033/ ├── manifest.json ...--- `39-42`: **Grammar nit: “30min” → “30 min”** Small readability fix. ```diff -- **Efficiency**: Average time from task start to PR < 30min for simple subtasks +- **Efficiency**: Average time from task start to PR < 30 min for simple subtasks
909-912: Fix list indentation (MD005/MD007)Align the final list item’s indent with peers.
- - Surgical Test Generator: .claude/agents/surgical-test-generator.md +- Surgical Test Generator: .claude/agents/surgical-test-generator.mdapps/cli/src/commands/autopilot/abort.command.ts (1)
14-14: Lazy‑load inquirer only when prompting to cut startup cost.Import inquirer dynamically inside the confirmation branch; remove the top‑level import. Small perf win for non‑interactive/JSON flows.
-import inquirer from 'inquirer'; @@ - // Confirm abort if not forced or in JSON mode + // Confirm abort if not forced and not in JSON mode if (!mergedOptions.force && !mergedOptions.json) { + const { default: inquirer } = await import('inquirer'); const { confirmed } = await inquirer.prompt([Also applies to: 69-87
apps/cli/src/commands/autopilot/next.command.ts (1)
94-111: Make test command configurable.Hard‑coding "npm test" limits portability (pnpm/yarn/custom scripts). Read from config/env and default to npm if absent.
apps/mcp/src/tools/autopilot/commit.tool.ts (2)
17-31: Let withNormalizedProjectRoot supply projectRoot; make it optional in schema.Schema requires projectRoot, preventing env/session fallback. Mark it optional.
Based on learnings
-const CommitSchema = z.object({ - projectRoot: z - .string() - .describe('Absolute path to the project root directory'), +const CommitSchema = z.object({ + projectRoot: z + .string() + .optional() + .describe('Absolute path to the project root directory (optional; can be inferred from TASK_MASTER_PROJECT_ROOT or session)'),Also applies to: 44-46
154-170: Dead/unreached branch for commit message type.You already enforce tddPhase === 'COMMIT'; the else branch is unreachable. Simplify to a fixed type (e.g., 'feat') or derive from context (subtask metadata).
apps/cli/src/commands/autopilot/complete.command.ts (1)
31-34: --coverage is unused. Either wire it into output/validation or drop the flag.Currently accepted but ignored, which confuses users.
Option A (use it): include coverage in success payloads.
- .option('-c, --coverage <percent>', 'Coverage percentage') + .option('-c, --coverage <percent>', 'Coverage percentage') @@ - formatter.success('RED phase completed', { + formatter.success('RED phase completed', { nextPhase: 'GREEN', - testResults, + testResults, + coverage: mergedOptions.coverage, subtask: currentSubtask?.title }); @@ - formatter.success('GREEN phase completed', { + formatter.success('GREEN phase completed', { nextPhase: 'COMMIT', - testResults, + testResults, + coverage: mergedOptions.coverage, subtask: currentSubtask?.title, suggestion: 'Run: autopilot commit' });Option B (remove it): delete the flag until it’s implemented.
Also applies to: 14-17
apps/cli/src/commands/autopilot/commit.command.ts (1)
101-116: Allow custom commit message when provided.Respect -m/--message, else use generator.
Apply:
-// Generate commit message -const messageGenerator = createCommitMessageGenerator(); -const testResults = state.context.lastTestResults; -const commitMessage = messageGenerator.generateMessage({ +// Generate or use custom commit message +const messageGenerator = createCommitMessageGenerator(); +const testResults = state.context.lastTestResults; +const commitMessage = mergedOptions.message ?? messageGenerator.generateMessage({ type: 'feat', description: currentSubtask.title, changedFiles, taskId: state.context.taskId, phase: 'TDD', tag: (state.context.metadata.tag as string) || undefined, testsPassing: testResults?.passed, testsFailing: testResults?.failed, coveragePercent: undefined // Could be added if available });.taskmaster/docs/tdd-workflow-phase-1-core-rails.md (7)
326-333: Integrity risk: trusting AI‑reported test counts without local verificationConsider an optional verification step (feature‑flagged) where TaskMaster re‑runs a lightweight test command or validates a machine‑readable report (e.g., JUnit/TAP) before allowing COMMIT. This prevents false positives from misreported results.
Would you like a minimal design for “tm autopilot verify” (runs project test command with timeout, parses counts, compares to reported)?
1076-1083: Tautology: “CLI Interface”Prefer “CLI” to avoid “Command Line Interface Interface.” Rename the section header.
66-110: Add language to fenced blockSpecify a language (e.g., text) for the flow block to satisfy markdownlint MD040.
-``` +```text ...--- `354-373`: **Add language to fenced block** Specify language (text) for the directory tree. ```diff -``` +```text ...--- `474-481`: **Add language to fenced block** Specify language (text) for the project tree. ```diff -``` +```text ...--- `516-527`: **Add language to fenced block** Specify language (text or git-commit) for the commit template. ```diff -``` +```text ...--- `530-543`: **Add language to fenced block** Specify language (text) for the example commit. ```diff -``` +```text ...</blockquote></details> <details> <summary>apps/cli/src/commands/next.command.ts (2)</summary><blockquote> `109-114`: **Validate project path before initializing core** Fail fast if the provided project path doesn’t exist or isn’t a directory. ```diff +import fs from 'node:fs'; ... private async initializeCore(projectRoot: string): Promise<void> { if (!this.tmCore) { const resolved = path.resolve(projectRoot); + if (!fs.existsSync(resolved) || !fs.statSync(resolved).isDirectory()) { + throw new Error(`Project root does not exist or is not a directory: ${resolved}`); + } this.tmCore = await createTaskMasterCore({ projectPath: resolved }); } }
199-201: CLI hint references “task-master”; ensure correct binary nameIf the binary is “tm”, update the hint (or derive dynamically).
-`Tip: Try` task-master list --status pending +`Tip: Try` tm list --status pendingPlease confirm the canonical CLI binary (“tm” vs “task-master”) so we can update all help texts consistently.
mcp-server/src/tools/tool-registry.js (2)
1-4: Doc mismatch: 36 vs 44 toolsUpdate the header comments to reflect the current count (44).
Also applies to: 56-58
58-103: Canonicalize tool name to snake_case with backward‑compat aliasGuideline: use snake_case for tool names exposed to clients. Keep 'response-language' as a legacy alias but prefer 'response_language' as the canonical key.
export const toolRegistry = { initialize_project: registerInitializeProjectTool, models: registerModelsTool, rules: registerRulesTool, - parse_prd: registerParsePRDTool, - 'response-language': registerResponseLanguageTool, + parse_prd: registerParsePRDTool, + response_language: registerResponseLanguageTool, // canonical + 'response-language': registerResponseLanguageTool, // legacy alias analyze_project_complexity: registerAnalyzeProjectComplexityTool, ... };As per coding guidelines.
packages/tm-core/src/git/git-adapter.test.ts (2)
348-356: Prefer top‑level import for simple‑git in testsAvoid dynamic import() in tests per guidelines; import once at top for consistency and performance.
- simpleGit = (await import('simple-git')).default; + // at top of file: + import simpleGit from 'simple-git';As per coding guidelines.
Also applies to: 600-608, 840-848, 1069-1077
238-246: Use rejects assertion instead of manual fail()Make failures deterministic and readable.
-try { - await gitAdapter.ensureGitRepository(); - fail('Should have thrown error'); -} catch (error) { - expect(error.message).toContain('not a git repository'); -} +await expect(gitAdapter.ensureGitRepository()).rejects.toThrow('not a git repository');Also applies to: 586-592
mcp-server/src/tools/index.js (1)
73-87: Normalize custom tool names: expand aliasing and DRY the resolutionResolution loop is thorough; consider centralizing alias/normalization (underscore↔hyphen, case‑insensitive) into a helper to reduce duplication and ease future aliases.
I can extract resolveToolName(toolName, registryKeys, aliasMap) and add unit tests.
Also applies to: 91-118
apps/cli/src/commands/autopilot/shared.ts (2)
130-139: Improve array/depth handling in text outputArrays print as object entries; large/deep objects can spam the console. Consider array-aware formatting and max depth.
Also applies to: 144-153
255-262: Preserve existing attempts and normalize statusesIf subtask has attempts/status variations, keep them; default to 0 only when missing; map common status values.
Apply:
return task.subtasks.map((subtask: any) => ({ id: subtask.id, title: subtask.title, - status: subtask.status === 'done' ? 'completed' : 'pending', - attempts: 0, + status: + subtask.status === 'done' || subtask.status === 'completed' + ? 'completed' + : 'pending', + attempts: + typeof subtask.attempts === 'number' && Number.isFinite(subtask.attempts) + ? subtask.attempts + : 0, maxAttempts }));packages/tm-core/src/git/commit-message-generator.ts (1)
92-108: Optional: pre‑validate before renderingConsider validating type/description and throwing early in generateMessage to avoid rendering bad messages.
apps/cli/src/commands/context.command.ts (1)
434-439: Await on sync method (cosmetic)clearContext() is synchronous now; awaiting is unnecessary.
Apply:
- await this.authManager.clearContext(); + this.authManager.clearContext();packages/tm-core/src/git/git-adapter.ts (2)
562-612: Commit gating depends on staged detectioncreateCommit() relies on hasStagedChanges(); once fixed, ensure error text remains accurate; consider fallback to “git diff --cached --name-only”.
676-694: Default branch detection could use origin/HEAD (optional)Optionally read symbolic‑ref of origin/HEAD when available before fallback.
| ```typescript | ||
| function getProjectStoragePath(projectRoot: string): string { | ||
| const normalized = projectRoot | ||
| .replace(/\//g, '-') | ||
| .replace(/^-/, '') | ||
|
|
||
| return path.join(os.homedir(), '.taskmaster', 'projects', normalized) | ||
| // Example: ~/.taskmaster/projects/-Volumes-Workspace-contrib-task-master-claude-task-master | ||
| } | ||
| ``` |
There was a problem hiding this comment.
Windows-safe project path normalization
Replace forward-slash-only replace with a cross‑platform sanitizer; include backslashes and illegal filename chars (Windows).
-function getProjectStoragePath(projectRoot: string): string {
- const normalized = projectRoot
- .replace(/\//g, '-')
- .replace(/^-/, '')
-
- return path.join(os.homedir(), '.taskmaster', 'projects', normalized)
-}
+function getProjectStoragePath(projectRoot: string): string {
+ const normalized = projectRoot
+ .replace(/[\\/]/g, '-') // slashes (win/unix)
+ .replace(/^[-]+/, '') // leading dashes
+ .replace(/[:*?"<>|]/g, '-') // win-illegal chars
+ .replace(/\s+/g, '-') // collapse spaces
+ .toLowerCase();
+ return path.join(os.homedir(), '.taskmaster', 'projects', normalized);
+}🤖 Prompt for AI Agents
In .taskmaster/docs/tdd-workflow-phase-1-core-rails.md around lines 376 to 385,
the project path normalization only replaces forward slashes and can produce
invalid Windows paths; update the sanitizer to replace both forward-slash and
backslash characters and also strip or replace characters illegal in Windows
filenames (<>:"/\\|?*), trim leading separators or dots, collapse repeated
separators into a single hyphen or underscore, and optionally truncate to a safe
length; ensure the final returned path is built with path.join(os.homedir(),
'.taskmaster', 'projects', sanitizedName) so it is cross-platform safe.
| async function resumeWorkflow(): Promise<void> { | ||
| // 1. Find active run | ||
| const currentRun = await loadJSON('~/.taskmaster/projects/<project>/tags/<tag>/current-run.json') | ||
|
|
||
| // 2. Load state from that run | ||
| const state = await loadJSON(`~/.taskmaster/projects/<project>/runs/${currentRun.runId}/state.json`) | ||
|
|
||
| // 3. Continue from checkpoint | ||
| return orchestrator.resumeFrom(state) | ||
| } | ||
| ``` |
There was a problem hiding this comment.
Don’t use “~” literally in paths
Tilde isn’t expanded by Node APIs. Document that helpers expand to os.homedir() or show absolute path construction.
🤖 Prompt for AI Agents
In .taskmaster/docs/tdd-workflow-phase-1-core-rails.md around lines 496 to 506,
the example uses "~" literally in file paths which Node APIs do not expand;
update the doc to either state that the provided loadJSON helper expands "~" to
the user home (os.homedir()) or replace the example with an explicit
absolute-path construction using the Node home directory (e.g., calling
os.homedir() and path.join) so readers see the correct runtime behavior; make
the note brief and show the corrected path usage in the example or add one line
of explanation that the helper expands "~" to os.homedir() before calling Node
filesystem APIs.
| ```typescript | ||
| function determineScope(files: string[]): string { | ||
| // Extract common scope from changed files | ||
| const scopes = files.map(f => { | ||
| if (f.startsWith('apps/cli/')) return 'cli' | ||
| if (f.startsWith('packages/tm-core/')) return 'core' | ||
| if (f.startsWith('packages/tm-mcp/')) return 'mcp' | ||
| return 'misc' | ||
| }) | ||
|
|
||
| // Use most common scope | ||
| return mode(scopes) | ||
| } |
There was a problem hiding this comment.
Undefined helper “mode(scopes)” in example
The snippet references mode(scopes) without defining it. Add a brief implementation or clarify it’s pseudocode.
🤖 Prompt for AI Agents
In .taskmaster/docs/tdd-workflow-phase-1-core-rails.md around lines 554 to 566
the example calls an undefined helper mode(scopes); implement or clarify it by
adding a brief helper that returns the most-frequent string from the array
(e.g., count frequencies, return the item with highest count, with a
deterministic tie-breaker like first-seen) or mark the call as pseudocode and
show a one-line implementation comment; ensure the helper signature matches the
example (takes string[] and returns string) and mention tie-break behavior.
| **PytestAdapter** (`packages/tm-core/src/services/test-adapters/pytest-adapter.ts`): | ||
| ```typescript | ||
| class PytestAdapter implements FrameworkAdapter { | ||
| async runTargeted(pattern: string): Promise<TestResults> { | ||
| const output = await exec(`pytest ${pattern} --json-report`) | ||
| return this.parseResults(output) | ||
| } | ||
|
|
||
| async runAll(): Promise<TestResults> { | ||
| const output = await exec('pytest --cov --json-report') | ||
| return this.parseResults(output) | ||
| } | ||
|
|
||
| parseCoverage(output: string): Promise<CoverageReport> { | ||
| // Parse pytest-cov XML output | ||
| } | ||
|
|
||
| getTestFilePattern(): string { | ||
| return '**/test_*.py' | ||
| } | ||
|
|
||
| getTestFileExtension(): string { | ||
| return '.py' | ||
| } | ||
| } | ||
| ``` |
There was a problem hiding this comment.
Avoid shell injection in examples using exec.
Interpolating pattern/pane IDs into command strings is unsafe. Prefer argument arrays with a safe runner (execa/spawn) and sanitize inputs.
Example (TypeScript):
-const output = await exec(`pytest ${pattern} --json-report`)
+import { execa } from 'execa';
+const args = [pattern, '--json-report'];
+const { stdout } = await execa('pytest', args, { cwd: process.cwd() });
+const output = stdout;-const output = await exec(`go test ${pattern} -json`)
+const { stdout } = await execa('go', ['test', pattern, '-json']);-const paneId = await exec('tmux split-window -h -P -F "#{pane_id}"')
-await exec(`tmux send-keys -t ${paneId} "tm autopilot --executor-mode" Enter`)
+const { stdout: paneId } = await execa('tmux', ['split-window', '-h', '-P', '-F', '#{pane_id}']);
+await execa('tmux', ['send-keys', '-t', paneId.trim(), 'tm autopilot --executor-mode', 'Enter']);Also applies to: 81-106, 251-255
🤖 Prompt for AI Agents
.taskmaster/docs/tdd-workflow-phase-3-extensibility-guardrails.md around lines
54-79: the example shows using exec with interpolated strings which is
vulnerable to shell injection; update the adapter implementation to call a safe
process runner (e.g., execa or spawn) with the command and arguments as an array
instead of a single interpolated string, avoid shell:true, and validate/sanitize
the incoming pattern (whitelist or strict regex for expected file patterns)
before using it; apply the same change to the other examples referenced at lines
81-106 and 251-255 so all command invocations use argument arrays and input
validation.
| this.description('Create a commit for the completed GREEN phase').action( | ||
| async (options: CommitOptions) => { | ||
| await this.execute(options); | ||
| } | ||
| ); | ||
| } |
There was a problem hiding this comment.
Expose documented flags (--message, --files) on the CLI.
Docs/CHANGELOG show these flags, but the command doesn’t define them. Add Commander options and plumb them into execute().
Apply:
constructor() {
super('commit');
- this.description('Create a commit for the completed GREEN phase').action(
+ this.description('Create a commit for the completed GREEN phase')
+ .option('-m, --message <text>', 'Custom commit message')
+ .option('-f, --files <paths...>', 'Specific files to stage')
+ .action(
async (options: CommitOptions) => {
await this.execute(options);
}
);And update the type:
-type CommitOptions = AutopilotBaseOptions;
+type CommitOptions = AutopilotBaseOptions & {
+ message?: string;
+ files?: string[];
+};📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| this.description('Create a commit for the completed GREEN phase').action( | |
| async (options: CommitOptions) => { | |
| await this.execute(options); | |
| } | |
| ); | |
| } | |
| this.description('Create a commit for the completed GREEN phase') | |
| .option('-m, --message <text>', 'Custom commit message') | |
| .option('-f, --files <paths...>', 'Specific files to stage') | |
| .action( | |
| async (options: CommitOptions) => { | |
| await this.execute(options); | |
| } | |
| ); | |
| } |
🤖 Prompt for AI Agents
In apps/cli/src/commands/autopilot/commit.command.ts around lines 26 to 31, the
CLI command is missing the documented Commander options --message and --files;
add .option(...) calls for --message <msg> and --files <files...> (or a
comma-separated string parsed into an array) to the command builder, update the
CommitOptions type to include message?: string and files?: string[] (or matching
the chosen parse shape), and pass those fields through into
this.execute(options) so execute receives the supplied message and files flags.
| import { GitAdapter } from '../../../../../packages/tm-core/src/git/git-adapter.js'; | ||
|
|
There was a problem hiding this comment.
Incorrect and brittle import path
Import the SUT relatively from the same folder (and avoid hardcoding repo paths). This also plays nicer with ts-jest.
-import { GitAdapter } from '../../../../../packages/tm-core/src/git/git-adapter.js';
+import { GitAdapter } from './git-adapter.js';If your test runner resolves TypeScript without extensions, prefer:
+// or
+import { GitAdapter } from './git-adapter';📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| import { GitAdapter } from '../../../../../packages/tm-core/src/git/git-adapter.js'; | |
| import { GitAdapter } from './git-adapter.js'; |
🤖 Prompt for AI Agents
In packages/tm-core/src/git/git-adapter.test.ts around lines 12 to 13, the test
imports the SUT using a hardcoded repo-relative path which is brittle; replace
that import with a local relative import from the same folder (e.g. import {
GitAdapter } from './git-adapter' or './git-adapter.ts' depending on resolver)
and remove the absolute/hardcoded path and the .js extension so the test
runner/ts-jest can resolve the TypeScript module reliably.
| it('should throw error if git version check fails', async () => { | ||
| gitAdapter = new GitAdapter(testDir); | ||
|
|
||
| // Mock simple-git to throw error | ||
| const mockGit = { | ||
| version: jest.fn().mockRejectedValue(new Error('git not found')) | ||
| }; | ||
| gitAdapter.git = mockGit; | ||
|
|
||
| await expect(gitAdapter.validateGitInstallation()).rejects.toThrow( |
There was a problem hiding this comment.
Accessing private field git will fail TypeScript checks
Cast to any (test-only) or mock via public seams.
-const mockGit = { version: jest.fn().mockRejectedValue(new Error('git not found')) };
-gitAdapter.git = mockGit;
+const mockGit = { version: jest.fn().mockRejectedValue(new Error('git not found')) };
+(gitAdapter as any).git = mockGit;Alternatively, expose a factory seam for the simple-git instance in GitAdapter for easier testing.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| it('should throw error if git version check fails', async () => { | |
| gitAdapter = new GitAdapter(testDir); | |
| // Mock simple-git to throw error | |
| const mockGit = { | |
| version: jest.fn().mockRejectedValue(new Error('git not found')) | |
| }; | |
| gitAdapter.git = mockGit; | |
| await expect(gitAdapter.validateGitInstallation()).rejects.toThrow( | |
| it('should throw error if git version check fails', async () => { | |
| gitAdapter = new GitAdapter(testDir); | |
| // Mock simple-git to throw error | |
| const mockGit = { | |
| version: jest.fn().mockRejectedValue(new Error('git not found')) | |
| }; | |
| (gitAdapter as any).git = mockGit; | |
| await expect(gitAdapter.validateGitInstallation()).rejects.toThrow( |
🤖 Prompt for AI Agents
In packages/tm-core/src/git/git-adapter.test.ts around lines 103 to 112, the
test directly assigns a mock to the private field `git`, which breaks TypeScript
checks; either cast the adapter to `any` when assigning the mock (e.g.
(gitAdapter as any).git = mockGit) or change the test to mock via public seams
(call a public factory or constructor parameter) — alternatively add a testing
seam in GitAdapter that exposes or injects the simple-git instance (e.g. a
protected/createGit method or constructor option) and use that seam in the test
to supply the mock.
| it('should provide clear error for permission denied', async () => { | ||
| // Create .git but make it inaccessible | ||
| await fs.ensureDir(path.join(testDir, '.git')); | ||
|
|
||
| gitAdapter = new GitAdapter(testDir); | ||
|
|
||
| try { | ||
| await fs.chmod(path.join(testDir, '.git'), 0o000); | ||
|
|
||
| await gitAdapter.isGitRepository(); | ||
| } catch (error) { | ||
| // Error handling | ||
| } finally { | ||
| // Restore permissions | ||
| await fs.chmod(path.join(testDir, '.git'), 0o755); | ||
| } | ||
| }); | ||
|
|
There was a problem hiding this comment.
Permission denied test: assert or platform‑gate
Add an assertion on the error or skip on Windows where chmod may not apply.
-} catch (error) {
- // Error handling
+} catch (error) {
+ if (process.platform === 'win32') {
+ return; // skip on Windows
+ }
+ expect(String(error.message || error)).toMatch(/permission|access/i);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| it('should provide clear error for permission denied', async () => { | |
| // Create .git but make it inaccessible | |
| await fs.ensureDir(path.join(testDir, '.git')); | |
| gitAdapter = new GitAdapter(testDir); | |
| try { | |
| await fs.chmod(path.join(testDir, '.git'), 0o000); | |
| await gitAdapter.isGitRepository(); | |
| } catch (error) { | |
| // Error handling | |
| } finally { | |
| // Restore permissions | |
| await fs.chmod(path.join(testDir, '.git'), 0o755); | |
| } | |
| }); | |
| it('should provide clear error for permission denied', async () => { | |
| // Create .git but make it inaccessible | |
| await fs.ensureDir(path.join(testDir, '.git')); | |
| gitAdapter = new GitAdapter(testDir); | |
| try { | |
| await fs.chmod(path.join(testDir, '.git'), 0o000); | |
| await gitAdapter.isGitRepository(); | |
| } catch (error) { | |
| if (process.platform === 'win32') { | |
| return; // skip on Windows | |
| } | |
| expect(String(error.message || error)).toMatch(/permission|access/i); | |
| } finally { | |
| // Restore permissions | |
| await fs.chmod(path.join(testDir, '.git'), 0o755); | |
| } | |
| }); |
🤖 Prompt for AI Agents
In packages/tm-core/src/git/git-adapter.test.ts around lines 279 to 296, the
permission-denied test currently swallows any error and is flaky on Windows;
update it to either skip on Windows or assert the thrown error explicitly: add a
platform gate at the start (if process.platform === 'win32' then test.skip or
return) or wrap the gitAdapter.isGitRepository() call in a try/catch that
captures the error and assert its type/code/message (e.g.,
expect(error.code).toBe('EACCES') or
expect(error.message).toMatch(/permission|EACCES/i)); also ensure the finally
block only attempts to restore permissions if chmod succeeded (guard with
try/catch) so cleanup won't throw.
| // Escape special regex characters (but not our placeholder or *) | ||
| regexPattern = regexPattern.replace(/[.+^${}()|[\]\\]/g, '\\$&'); | ||
|
|
||
| // Replace single * with [^/]* (matches anything except /) | ||
| regexPattern = regexPattern.replace(/\*/g, '[^/]*'); | ||
|
|
||
| // Replace placeholder with .* (matches anything including /) | ||
| regexPattern = regexPattern.replace(/§GLOBSTAR§/g, '.*'); | ||
|
|
||
| const regex = new RegExp(`^${regexPattern}$`); | ||
| return regex.test(filePath); | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Escape “?” and precompile patterns to mitigate ReDoS and speed matching.
You escape many regex metas, but “?” remains unescaped. Also, compiling a new RegExp per match is costly; precompile in constructor.
-// Escape special regex characters (but not our placeholder or *)
-regexPattern = regexPattern.replace(/[.+^${}()|[\]\\]/g, '\\$&');
+// Escape special regex characters (but not our placeholder or *)
+regexPattern = regexPattern.replace(/[?+.^${}()|[\]\\]/g, '\\$&');- Precompute an array of { regex: RegExp, scope: string } in the constructor from scopeMappings and use it in getMatchingScope().
- Add a max pattern length or whitelist (“*” / “**” only) for custom mappings to avoid pathological inputs.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Escape special regex characters (but not our placeholder or *) | |
| regexPattern = regexPattern.replace(/[.+^${}()|[\]\\]/g, '\\$&'); | |
| // Replace single * with [^/]* (matches anything except /) | |
| regexPattern = regexPattern.replace(/\*/g, '[^/]*'); | |
| // Replace placeholder with .* (matches anything including /) | |
| regexPattern = regexPattern.replace(/§GLOBSTAR§/g, '.*'); | |
| const regex = new RegExp(`^${regexPattern}$`); | |
| return regex.test(filePath); | |
| } | |
| // Escape special regex characters (but not our placeholder or *) | |
| regexPattern = regexPattern.replace(/[?+.^${}()|[\]\\]/g, '\\$&'); | |
| // Replace single * with [^/]* (matches anything except /) | |
| regexPattern = regexPattern.replace(/\*/g, '[^/]*'); | |
| // Replace placeholder with .* (matches anything including /) | |
| regexPattern = regexPattern.replace(/§GLOBSTAR§/g, '.*'); | |
| const regex = new RegExp(`^${regexPattern}$`); | |
| return regex.test(filePath); | |
| } |
🧰 Tools
🪛 ast-grep (0.39.6)
[warning] 200-200: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(^${regexPattern}$)
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html
(regexp-from-variable)
| beforeEach(() => { | ||
| templateEngine = new TemplateEngine(); | ||
| }); |
There was a problem hiding this comment.
🧩 Analysis chain
Tests assume placeholders are preserved, but default is false.
render() drops missing vars by default; these assertions will fail. Either enable preservePlaceholders in the test setup or change expectations.
-beforeEach(() => {
- templateEngine = new TemplateEngine();
-});
+beforeEach(() => {
+ templateEngine = new TemplateEngine({ preservePlaceholders: true });
+});If you prefer the current default behavior, update these expects to omit placeholders instead.
Also applies to: 53-58, 218-223
---
🏁 Script executed:
```shell
#!/bin/bash
# Find TemplateEngine class definition
find . -name "*.ts" -type f | grep -v node_modules | grep -v ".test.ts" | xargs grep -l "class TemplateEngine"
Length of output: 45
🏁 Script executed:
#!/bin/bash
# Inspect TemplateEngine constructor and preservePlaceholders default
find . -name "*.ts" -type f | grep -v node_modules | grep -v ".test.ts" | xargs grep -A 20 "class TemplateEngine"Length of output: 1726
🏁 Script executed:
#!/bin/bash
# Show test file at specified line ranges
cat packages/tm-core/src/git/template-engine.test.ts | sed -n '1,100p'Length of output: 2806
🏁 Script executed:
#!/bin/bash
# Show test file lines 53-58 and 218-223 context
sed -n '45,230p' packages/tm-core/src/git/template-engine.test.tsLength of output: 5570
🏁 Script executed:
#!/bin/bash
# Inspect render method implementation in TemplateEngine
sed -n '1,150p' packages/tm-core/src/git/template-engine.ts | grep -A 30 "render("Length of output: 621
🏁 Script executed:
#!/bin/bash
# Get more context on render method
grep -n "render(" packages/tm-core/src/git/template-engine.ts | head -5Length of output: 11
🏁 Script executed:
#!/bin/bash
# Inspect substituteVariables method which is called by render()
grep -n "substituteVariables" packages/tm-core/src/git/template-engine.tsLength of output: 92
🏁 Script executed:
#!/bin/bash
# Get full implementation of substituteVariables
sed -n '80,150p' packages/tm-core/src/git/template-engine.tsLength of output: 1599
🏁 Script executed:
#!/bin/bash
# Get complete substituteVariables implementation
sed -n '140,180p' packages/tm-core/src/git/template-engine.tsLength of output: 1173
Update TemplateEngine setup in beforeEach to enable placeholder preservation.
Tests expect missing variables to be preserved as placeholders (e.g., {{location}}), but the constructor defaults preservePlaceholders to false, causing render() to return empty strings. This will cause test failures at lines 53 and 218.
beforeEach(() => {
- templateEngine = new TemplateEngine();
+ templateEngine = new TemplateEngine({ preservePlaceholders: true });
});📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| beforeEach(() => { | |
| templateEngine = new TemplateEngine(); | |
| }); | |
| beforeEach(() => { | |
| templateEngine = new TemplateEngine({ preservePlaceholders: true }); | |
| }); |
🤖 Prompt for AI Agents
In packages/tm-core/src/git/template-engine.test.ts around lines 7 to 9, the
beforeEach creates TemplateEngine with the default preservePlaceholders=false so
missing variables are rendered as empty strings; update the setup to enable
placeholder preservation by instantiating TemplateEngine with
preservePlaceholders turned on (e.g. pass the option or call the appropriate
setter) so render() leaves unknown variables as placeholders and the tests at
lines 53 and 218 pass.
There was a problem hiding this comment.
Actionable comments posted: 43
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (10)
.env.example (1)
1-17: Missing requiredPROVIDER_API_KEYvariable per coding guidelines.The coding guidelines explicitly require adding the new
PROVIDER_API_KEYto.env.example, but this variable is missing from the file. This must be added as part of this release.Propose adding
PROVIDER_API_KEYafter the existing API key section:AZURE_OPENAI_API_KEY=YOUR_AZURE_KEY_HERE OLLAMA_API_KEY=YOUR_OLLAMA_API_KEY_HERE + PROVIDER_API_KEY=YOUR_PROVIDER_KEY_HERE # Google Vertex AI ConfigurationAs per coding guidelines.
mcp-server/src/tools/move-task.js (1)
186-199: Critical: Undefined variable references will cause runtime ReferenceError.This code path executes when
fromIds.length !== toIds.lengthANDfromIds.length === 1(lines 186–199 enter when theif (fromIds.length > 1)block is skipped). However, it references:
results(line 190)—only declared inside theif (fromIds.length > 1)block (line 134)skippedMoves(line 191)—never declared; the variable at line 144 is namedskippedThis will fail at runtime. Either this code is dead (and should be removed), or it needs access to scoped variables. Please clarify the intended behavior for single-ID mismatched-count moves.
CLAUDE.md (1)
21-33: Align the example import extension with the text.The comment says “Synchronous imports with .ts extension,” but the sample still imports
../src/my-class.js. Update the example to use the.tsextension (or adjust the wording) so the example matches the stated rule.packages/tm-core/src/auth/supabase-session-storage.ts (1)
81-81: Inconsistent usage ofallowExpiredparameter.The
getCredentials()calls are inconsistent:
- Line 81:
getCredentials({ allowExpired: true })- Line 105:
getCredentials()(no parameter)- Line 134:
getCredentials({ allowExpired: true })While the default for
allowExpiredistrue(enabling Supabase refresh flows), the inconsistent usage makes the code harder to maintain. Consider standardizing to either:
- Always rely on the default (remove explicit parameter everywhere), or
- Always be explicit (add parameter everywhere)
Based on learnings: The Supabase refresh flow requires
allowExpired: trueto retrieve expired tokens that have valid refresh tokens.Apply this diff to make the usage consistent:
getItem(key: string): string | null { // Supabase uses a specific key pattern for sessions if (key === STORAGE_KEY || key.includes('auth-token')) { try { - const credentials = this.store.getCredentials({ allowExpired: true }); + const credentials = this.store.getCredentials(); if (credentials && credentials.token) { // Build and return a session object from our stored credentials const session = this.buildSessionFromCredentials(credentials);removeItem(key: string): void { if (key === STORAGE_KEY || key.includes('auth-token')) { // Don't actually remove credentials, just clear the tokens // This preserves other data like selectedContext try { - const credentials = this.store.getCredentials({ allowExpired: true }); + const credentials = this.store.getCredentials(); if (credentials) { // Keep context but clear auth tokens const clearedCredentials: AuthCredentials = {Also applies to: 105-105, 134-134
apps/cli/src/commands/show.command.ts (1)
159-165: Handle 'auto' StorageType explicitly before castingYour casting to
Exclude<StorageType, 'auto'>is unsafe. ThegetStorageType()implementation inpackages/tm-core/src/services/task-service.tscan return'auto'when runtime resolution fails and config fallback is'auto'. ThedisplayCommandHeaderfunction explicitly requiresExclude<StorageType, 'auto'>, so passing'auto'violates its type contract.The
next.command.ts(lines 131–132) demonstrates the correct pattern: check for'auto'and throw an error. Apply the same defensive check here instead of casting:const storageType = this.tmCore.getStorageType(); if (storageType === 'auto') { throw new Error('Storage type must be resolved before use'); } return { task, found: task !== null, storageType };Fix both occurrences: lines 159–165 (single task) and 193–199 (multiple tasks).
mcp-server/src/index.js (1)
145-152: Bug: possible crash when session is falsy.In the else branch you reference session.server/session.context even when session is falsy, causing a runtime TypeError. Replace with server logger or guard.
- } else { - session.server.sendLoggingMessage({ - data: { - context: session.context, - message: `No MCP sessions available, providers not registered` - }, - level: 'warn' - }); - } + } else { + this.logger.warn('No MCP sessions available, providers not registered'); + return; + }apps/cli/src/commands/set-status.command.ts (2)
167-179: JSON/text output inconsistency on partial failures.On a per-task error you print an error JSON then still emit a “success” result, leading to conflicting outputs. Skip success output when any error occurred.
- // Display results - this.displayResults(this.lastResult, options); + // Display results only when fully successful to avoid mixed outputs + if (!hasError) { + this.displayResults(this.lastResult, options); + }Follow-up (optional): In JSON mode, consider emitting a single aggregated object like
{ success: false, updatedTasks, error }.
273-286: Status mismatch confirmed: 'completed' missing from validation constants.The TaskStatus type includes 'completed' (8 total statuses), but VALID_TASK_STATUSES (set-status.command.ts:20-28) and TASK_STATUSES (tm-core/src/constants/index.ts:15-23) only define 7 statuses, omitting 'completed'. The getStatusDisplay method handles 'completed', creating a type-runtime inconsistency where the type permits a status that validation rejects.
Add 'completed' to both VALID_TASK_STATUSES and TASK_STATUSES, or remove it from the TaskStatus type definition and getStatusDisplay if it should not be supported.
packages/tm-core/src/auth/credential-store.ts (1)
249-253: Fix corrupt-file cleanup regex to match generated quarantine filenamesThe cleanup function at lines 249-253 uses regex
/^\d+$/which cannot match the corrupt file format generated at line 124:.corrupt-<timestamp>-<pid>-<randomstring>. This causes quarantined files to never be cleaned up, leading to disk space accumulation.Replace the validation regex to match the actual format:
- if (!/^\d+$/.test(suffix)) continue; // Fixed regex, not from variable input + const m = suffix.match(/^(\d+)-\d+-[a-z0-9]+$/i); + if (!m) continue;apps/cli/src/commands/auth.command.ts (1)
172-239: Fix core credential-store to support non-expiring API keysThe concern is confirmed as critical.
getCredentials()inpackages/tm-core/src/auth/credential-store.tsreturnsnullwhenexpiresAtis absent or undefined (lines 88–95). This recent change breaks API key support: the test at line 537–538 states "Credentials without expiry are considered invalid," butdisplayStatus()explicitly handles API keys with"Expires: Never (API key)"(lines 213–218 of auth.command.ts), which is now unreachable.Action required:
Modifycredential-store.tsto treat missingexpiresAtas non-expiring (valid indefinitely) rather than invalid. Either:
- Skip the null return when
expiresAtMs === undefinedand allow the credential through, or- Distinguish API keys from tokens and permit missing
expiresAtfor API key types.
♻️ Duplicate comments (5)
mcp-server/src/tools/rules.js (1)
6-8: Consistent zod/v3 migration pattern.The import change follows the same pattern established in other MCP tool files, maintaining consistency across the codebase.
mcp-server/src/tools/fix-dependencies.js (1)
6-8: Consistent zod/v3 migration pattern.The import change follows the same pattern established in other MCP tool files.
mcp-server/src/tools/scope-down.js (1)
6-8: Consistent zod/v3 migration pattern.The import change follows the same pattern established in other MCP tool files.
mcp-server/src/tools/get-task.js (1)
6-8: Consistent zod/v3 migration pattern.The import change follows the same pattern established in other MCP tool files.
mcp-server/src/tools/parse-prd.js (1)
6-8: Consistent zod/v3 migration pattern.The import change follows the same pattern established in other MCP tool files.
| { | ||
| "mode": "exit", | ||
| "tag": "rc", | ||
| "initialVersions": { | ||
| "task-master-ai": "0.29.0", | ||
| "@tm/cli": "", | ||
| "docs": "0.0.6", | ||
| "extension": "0.25.6", | ||
| "@tm/mcp": "0.28.0-rc.2", | ||
| "@tm/ai-sdk-provider-grok-cli": "", | ||
| "@tm/build-config": "", | ||
| "@tm/claude-code-plugin": "0.0.2", | ||
| "@tm/core": "" | ||
| }, | ||
| "changesets": [ | ||
| "dirty-hairs-know", | ||
| "fix-parent-directory-traversal", | ||
| "fix-warning-box-alignment", | ||
| "kind-lines-melt", | ||
| "light-owls-stay", | ||
| "metal-rocks-help", | ||
| "open-tips-notice", | ||
| "some-dodos-wonder" | ||
| ] | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
Verify pre-release configuration matches intended release type.
The pre.json has "mode": "exit" which indicates exiting pre-release mode, meaning the next release will be stable (0.30.0). However, the AI summary mentions "version bump to 0.30.0-rc.1" which would typically require "mode": "pre" instead.
Clarify:
- Is this PR releasing stable 0.30.0 (exiting RC)? If yes, mode "exit" is correct.
- Is this PR releasing 0.30.0-rc.1 (entering/continuing RC)? If yes, mode should be "pre".
Run the following script to check the actual version in package.json:
🏁 Script executed:
#!/bin/bash
# Check the version field in package.json to confirm release type
jq -r '.version' package.jsonLength of output: 11
Fix pre.json mode: should be "pre" not "exit".
The package.json contains version 0.30.0-rc.1 (RC pre-release), but .changeset/pre.json has "mode": "exit". These contradict each other.
Mode "exit" indicates the system is exiting pre-release mode, which means the next version should be stable (0.30.0). However, the current version is still an RC. Change "mode": "exit" to "mode": "pre" to properly reflect that the system should remain in pre-release mode while the version is 0.30.0-rc.1.
🤖 Prompt for AI Agents
In .changeset/pre.json lines 1-25, the "mode" is set to "exit" which contradicts
the repo being in an RC pre-release; change the "mode" value from "exit" to
"pre" so the changeset remains in pre-release mode (ensure the tag remains "rc"
and the rest of the file is unchanged).
| > **Learn more:** [TDD Workflow Quickstart Guide](https://dev.task-master.dev/tdd-workflow/quickstart) | ||
|
|
||
| This release enables AI agents to autonomously execute test-driven development workflows with full state management and recovery capabilities. |
There was a problem hiding this comment.
🧩 Analysis chain
Verify external link.
The “Learn more” link uses dev.task-master.dev; ensure it’s correct and public before release notes pick it up.
🌐 Web query:
Is https://dev.task-master.dev/tdd-workflow/quickstart the correct public docs URL for Task Master?
💡 Result:
No. The public docs live at docs.task-master.dev. The Quick Start page is here: https://docs.task-master.dev/getting-started/quick-start/quick-start. [1][2]
Sources:
[1] Task Master docs home.
[2] Task Master Quick Start page.
Update external link to correct public docs URL.
The URL https://dev.task-master.dev/tdd-workflow/quickstart uses the wrong domain and path. The correct public docs live at docs.task-master.dev, and the Quick Start page is at https://docs.task-master.dev/getting-started/quick-start/quick-start.
Update the "Learn more" link in the changeset before release.
🤖 Prompt for AI Agents
In .changeset/some-dodos-wonder.md around lines 34 to 36, the "Learn more" link
points to the wrong domain/path
(https://dev.task-master.dev/tdd-workflow/quickstart); update the markdown link
target to the correct public docs URL
https://docs.task-master.dev/getting-started/quick-start/quick-start so the
"Learn more" anchor points to that URL.
| "provider": "claude-code", | ||
| "modelId": "sonnet", |
There was a problem hiding this comment.
Manual config edits violate coding guidelines.
The changes to provider and modelId appear to be manual edits to .taskmaster/config.json, which violates the project's coding guidelines.
As per coding guidelines: "Do not manually edit the .taskmaster/config.json file. Use the included commands either in the MCP or CLI format as needed."
Please use the task-master models CLI command or the models MCP tool to configure the provider and model settings instead of manual edits.
Additionally, verify that:
claude-codeis a valid provider namesonnetis a valid modelId for that provider
As per coding guidelines.
🤖 Prompt for AI Agents
.taskmaster/config.json lines 4-5: the provider and modelId were manually edited
which violates project guidelines; instead run the supported configuration
flow—use the 'task-master models' CLI command or the 'models' MCP tool to set
the provider and modelId, e.g., run the CLI or MCP command to select the
provider and model (do not edit the JSON by hand), and before applying verify
that 'claude-code' is a valid registered provider and 'sonnet' is a valid
modelId for that provider (check the providers list via the CLI/MCP or provider
docs), then re-run the CLI/MCP command to persist the change safely.
| ```bash | ||
| # Start workflow - creates branch, initializes state | ||
| tm autopilot start <taskId> [options] | ||
| --branch <name> # Override branch name | ||
| --no-confirm # Skip confirmations | ||
| --max-attempts <n> # Override max GREEN attempts | ||
|
|
||
| # Get next action from state | ||
| tm autopilot next [options] | ||
| --json # Output as JSON for parsing | ||
|
|
||
| # Complete a phase and report test results | ||
| tm autopilot complete <phase> <subtaskId> --results="<passed:n,failed:n>" [options] | ||
| # phase: red | green | ||
| --results <passed:n,failed:n> # Required: test results from AI | ||
| --coverage <percentage> # Optional: coverage percentage | ||
| --files <file1,file2> # Optional: files changed (auto-detected if omitted) | ||
|
|
||
| # Create commit (called by AI after GREEN passes) | ||
| tm autopilot commit <subtaskId> [options] | ||
| --message <msg> # Override commit message | ||
|
|
||
| # Resume from interrupted run | ||
| tm autopilot resume [options] | ||
| --run-id <id> # Specific run to resume | ||
|
|
||
| # Get current status | ||
| tm autopilot status | ||
| --json # Output as JSON | ||
|
|
||
| # Watch activity log in real-time | ||
| tm autopilot watch | ||
|
|
||
| # Abort current run | ||
| tm autopilot abort [options] | ||
| --cleanup # Delete branch and state | ||
| ``` |
There was a problem hiding this comment.
CLI docs diverge from implemented commands (complete/commit/resume/abort/watch)
- complete: Code infers phase/current subtask from state and expects JSON via --results, not positional phase/subtask.
- commit: Implementation uses current subtask; no positional .
- resume: No --run-id option wired in provided code.
- abort: Implements --force; no --cleanup flag shown.
- watch: Command not present.
Align usage to avoid user confusion.
Proposed adjustments (illustrative):
-# Complete a phase and report test results
-tm autopilot complete <phase> <subtaskId> --results="<passed:n,failed:n>" [options]
- # phase: red | green
- --results <passed:n,failed:n> # Required: test results from AI
+# Complete the current phase and report test results (phase inferred)
+tm autopilot complete --results='{"total":10,"passed":9,"failed":1,"skipped":0}' [options]
+ # Results must be JSON (see --results help)
--coverage <percentage> # Optional: coverage percentage
- --files <file1,file2> # Optional: files changed (auto-detected if omitted)
-# Create commit (called by AI after GREEN passes)
-tm autopilot commit <subtaskId> [options]
+# Create commit (called by AI after GREEN passes; subtask inferred)
+tm autopilot commit [options]
-# Resume from interrupted run
-tm autopilot resume [options]
- --run-id <id> # Specific run to resume
+# Resume from interrupted run
+tm autopilot resume
-# Watch activity log in real-time
-tm autopilot watch
+# (watch command coming in Phase 2)
-# Abort current run
-tm autopilot abort [options]
- --cleanup # Delete branch and state
+# Abort current run
+tm autopilot abort [--force]🤖 Prompt for AI Agents
.taskmaster/docs/tdd-workflow-phase-1-core-rails.md lines 589-625: the CLI
examples currently diverge from the implemented tm autopilot commands—update the
docs so they match the code by changing the complete command to remove
positional <phase> and <subtaskId> and document that it reads phase/subtask from
state and accepts --results as JSON (show example JSON string), update commit to
show no positional <subtaskId> (it uses current subtask) and demonstrate
--message usage, remove the undocumented --run-id from resume (or document it
only if implemented), change abort to show --force instead of --cleanup (or
document both if code supports both), and remove references to a non-existent
watch command; ensure flag names, required/optional markers, and examples
reflect the actual implementation.
| # After AI generates tests and runs them | ||
| tm autopilot complete red 1.1 --results="failed:3,passed:0" | ||
|
|
||
| # After AI implements code and runs tests | ||
| tm autopilot complete green 1.1 --results="passed:3,failed:0" --coverage="92" | ||
|
|
||
| # With existing passing tests | ||
| tm autopilot complete red 1.1 --results="failed:3,passed:12" | ||
| ``` |
There was a problem hiding this comment.
Results format examples should be JSON (matches CompleteCommand parser)
Update examples to valid JSON payloads.
-tm autopilot complete red 1.1 --results="failed:3,passed:0"
+tm autopilot complete --results='{"total":3,"passed":0,"failed":3,"skipped":0}'
-tm autopilot complete green 1.1 --results="passed:3,failed:0" --coverage="92"
+tm autopilot complete --results='{"total":3,"passed":3,"failed":0,"skipped":0}' --coverage="92"Also applies to: 675-676
🤖 Prompt for AI Agents
.taskmaster/docs/tdd-workflow-phase-1-core-rails.md around lines 671-679 (and
also update 675-676): the example result payloads are plain text and not valid
JSON as required by the CompleteCommand parser; replace the three example CLI
--results/--coverage usages with equivalent JSON-formatted payloads (e.g., use
--results='{"failed":3,"passed":0}' and --results='{"passed":3,"failed":0}' with
--coverage='92' as appropriate, and for the mixed case use
--results='{"failed":3,"passed":12}') so the examples match the parser's
expected JSON format.
| it('should provide clear error for permission denied', async () => { | ||
| // Create .git but make it inaccessible | ||
| await fs.ensureDir(path.join(testDir, '.git')); | ||
|
|
||
| gitAdapter = new GitAdapter(testDir); | ||
|
|
||
| try { | ||
| await fs.chmod(path.join(testDir, '.git'), 0o000); | ||
|
|
||
| await gitAdapter.isGitRepository(); | ||
| } catch (error) { | ||
| // Error handling | ||
| } finally { | ||
| // Restore permissions | ||
| await fs.chmod(path.join(testDir, '.git'), 0o755); | ||
| } | ||
| }); |
There was a problem hiding this comment.
No-op/flaky test for permission errors.
Test swallows errors and never asserts. Also chmod 000 may fail on Windows. Either assert an error or skip conditionally.
-try {
- await fs.chmod(path.join(testDir, '.git'), 0o000);
- await gitAdapter.isGitRepository();
-} catch (error) {
- // Error handling
-} finally {
+try {
+ await fs.chmod(path.join(testDir, '.git'), 0o000);
+ await expect(gitAdapter.getRepositoryRoot()).rejects.toThrow();
+} finally {
await fs.chmod(path.join(testDir, '.git'), 0o755);
}Optionally guard Windows:
if (process.platform === 'win32') { return; } // skip🤖 Prompt for AI Agents
In packages/tm-core/src/git/git-adapter.test.ts around lines 279-295, the test
for permission denied is a no-op and swallows errors (plus chmod 000 is flaky on
Windows); modify it to either skip on Windows (if process.platform === 'win32'
return) or guard with os-specific logic, and replace the empty try/catch with a
proper assertion — e.g., set permissions, call gitAdapter.isGitRepository() and
assert it rejects with the expected permission error (or use
expect(...).rejects.toThrow()), ensure the finally block still restores
permissions, and do not swallow the caught error (fail the test if no error is
thrown).
| options: { | ||
| metadata?: Record<string, string>; | ||
| allowEmpty?: boolean; | ||
| enforceNonDefaultBranch?: boolean; | ||
| force?: boolean; | ||
| } = {} |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Extend options to control signing.
- enforceNonDefaultBranch?: boolean;
- force?: boolean;
+ enforceNonDefaultBranch?: boolean;
+ force?: boolean;
+ gpgSign?: boolean;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| options: { | |
| metadata?: Record<string, string>; | |
| allowEmpty?: boolean; | |
| enforceNonDefaultBranch?: boolean; | |
| force?: boolean; | |
| } = {} | |
| options: { | |
| metadata?: Record<string, string>; | |
| allowEmpty?: boolean; | |
| enforceNonDefaultBranch?: boolean; | |
| force?: boolean; | |
| gpgSign?: boolean; | |
| } = {} |
🤖 Prompt for AI Agents
In packages/tm-core/src/git/git-adapter.ts around lines 564-569, extend the
options type so callers can control commit signing: add optional properties like
sign?: boolean and signingKey?: string (or gpgSign?: boolean and gpgKey?: string
if that matches project naming) to the options definition, update any default
value handling to keep existing behavior when these fields are undefined, and
propagate these new fields into the places where commits are created (pass the
sign/signingKey flags into the git invocation or underlying library call and
ensure types align). Ensure unit/TS checks still pass and no other option shapes
are broken.
| async isDefaultBranch(branchName: string): Promise<boolean> { | ||
| const defaultBranches = ['main', 'master', 'develop']; | ||
| return defaultBranches.includes(branchName); | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Compute “default” via the detector instead of a hard-coded list.
- async isDefaultBranch(branchName: string): Promise<boolean> {
- const defaultBranches = ['main', 'master', 'develop'];
- return defaultBranches.includes(branchName);
- }
+ async isDefaultBranch(branchName: string): Promise<boolean> {
+ const def = await this.getDefaultBranch();
+ return branchName === def;
+ }🤖 Prompt for AI Agents
In packages/tm-core/src/git/git-adapter.ts around lines 709-712, the method uses
a hard-coded list to decide if a branch is “default”; replace that with the
repository’s default-branch detector. Call the adapter’s detector (for example
use an existing this.detector.isDefaultBranch(branchName) if available, or await
this.detector.getDefaultBranch(...) and compare to branchName) and return its
result so the logic derives the default branch dynamically rather than from a
static array.
| private matchesPattern(filePath: string, pattern: string): boolean { | ||
| // Replace ** first with a unique placeholder | ||
| let regexPattern = pattern.replace(/\*\*/g, '§GLOBSTAR§'); | ||
|
|
||
| // Escape special regex characters (but not our placeholder or *) | ||
| regexPattern = regexPattern.replace(/[.+^${}()|[\]\\]/g, '\\$&'); | ||
|
|
||
| // Replace single * with [^/]* (matches anything except /) | ||
| regexPattern = regexPattern.replace(/\*/g, '[^/]*'); | ||
|
|
||
| // Replace placeholder with .* (matches anything including /) | ||
| regexPattern = regexPattern.replace(/§GLOBSTAR§/g, '.*'); | ||
|
|
||
| const regex = new RegExp(`^${regexPattern}$`); | ||
| return regex.test(filePath); | ||
| } |
There was a problem hiding this comment.
Variable-driven regex construction: consider hardened glob matching.
Custom glob-to-regex can enable ReDoS with adversarial patterns and is easy to get subtly wrong. Prefer picomatch/minimatch to parse globs safely and efficiently.
If adding a dep is acceptable, replace with picomatch:
- private toRegex(pattern: string): RegExp {
- // ...manual conversion...
- return new RegExp(`^${regexPattern}$`);
- }
+ // Using picomatch
+ import picomatch from 'picomatch';
+ private toRegex(pattern: string): RegExp {
+ return picomatch.makeRe(pattern, { dot: true });
+ }At minimum, bound pattern length and validate with a safe-regex check before compiling.
Committable suggestion skipped: line range outside the PR's diff.
🧰 Tools
🪛 ast-grep (0.39.6)
[warning] 200-200: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(^${regexPattern}$)
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html
(regexp-from-variable)
🤖 Prompt for AI Agents
In packages/tm-core/src/git/scope-detector.ts around lines 188-203, the custom
glob-to-regex builder is brittle and can lead to ReDoS or incorrect matches;
replace this implementation with a battle-tested glob library (prefer picomatch
or minimatch) to perform pattern matching and use its isMatch API instead of
building regexes manually; if adding a dependency is not allowed, at minimum
enforce a maximum pattern length (e.g. 256 chars), validate the pattern
characters, and run a safe-regex check or timeout-protected regex compile/test
before creating RegExp to avoid catastrophic backtracking.
| beforeEach(() => { | ||
| templateEngine = new TemplateEngine(); | ||
| }); |
There was a problem hiding this comment.
Tests assume placeholders are preserved; initialize TemplateEngine accordingly.
Default preservePlaceholders is false, but several tests expect placeholders to remain. Initialize with preservePlaceholders: true in beforeEach.
Apply this diff:
beforeEach(() => {
- templateEngine = new TemplateEngine();
+ templateEngine = new TemplateEngine({ preservePlaceholders: true });
});This aligns expectations in "missing variables" and "empty variables object" tests.
Also applies to: 53-58, 218-223
🤖 Prompt for AI Agents
In packages/tm-core/src/git/template-engine.test.ts around lines 7-9 (and also
update the similar beforeEach blocks at 53-58 and 218-223), the test setup
constructs TemplateEngine with default preservePlaceholders=false but tests
expect placeholders to be preserved; change the beforeEach initializations to
create the TemplateEngine with preservePlaceholders: true so placeholders remain
during rendering. Ensure each beforeEach that currently does new
TemplateEngine() is replaced to pass the option { preservePlaceholders: true }.
| const status = await this.git.status(); | ||
| const staged = status.staged.length; | ||
| const modified = status.modified.length; | ||
| const deleted = status.deleted.length; | ||
| const untracked = status.not_added.length; | ||
| const totalChanges = staged + modified + deleted + untracked; | ||
|
|
||
| return { | ||
| isClean: status.isClean(), | ||
| staged, | ||
| modified, | ||
| deleted, | ||
| untracked, | ||
| totalChanges | ||
| }; | ||
| } |
There was a problem hiding this comment.
Correct getStatusSummary staged count to match porcelain semantics.
Use files[].index for staged; keep other counts as-is.
- const staged = status.staged.length;
- const modified = status.modified.length;
- const deleted = status.deleted.length;
- const untracked = status.not_added.length;
+ const staged = status.files.filter(f => f.index !== ' ').length;
+ const modified = status.modified.length;
+ const deleted = status.deleted.length;
+ const untracked = status.not_added.length;Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In packages/tm-core/src/git/git-adapter.ts around lines 296 to 311, the staged
count currently uses status.staged.length but should follow porcelain semantics
by counting entries where files[].index is set; change the staged calculation to
count status.files items with a non-empty index value, leave modified, deleted
and untracked counts as-is, and recompute totalChanges from those corrected
counts.
| // Add flags | ||
| commitArgs.push('--no-gpg-sign'); | ||
| if (options.allowEmpty) { | ||
| commitArgs.push('--allow-empty'); | ||
| } | ||
|
|
||
| await this.git.raw(commitArgs); |
There was a problem hiding this comment.
Don’t force --no-gpg-sign by default.
This breaks repos requiring signed commits and overrides user/org policies. Make it opt-in via an option.
- // Add flags
- commitArgs.push('--no-gpg-sign');
+ // Add flags
+ if (options.gpgSign === true) {
+ commitArgs.push('--gpg-sign');
+ } else if (options.gpgSign === false) {
+ commitArgs.push('--no-gpg-sign');
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Add flags | |
| commitArgs.push('--no-gpg-sign'); | |
| if (options.allowEmpty) { | |
| commitArgs.push('--allow-empty'); | |
| } | |
| await this.git.raw(commitArgs); | |
| // Add flags | |
| if (options.gpgSign === true) { | |
| commitArgs.push('--gpg-sign'); | |
| } else if (options.gpgSign === false) { | |
| commitArgs.push('--no-gpg-sign'); | |
| } | |
| if (options.allowEmpty) { | |
| commitArgs.push('--allow-empty'); | |
| } | |
| await this.git.raw(commitArgs); |
🤖 Prompt for AI Agents
In packages/tm-core/src/git/git-adapter.ts around lines 605 to 611, the code
currently always appends '--no-gpg-sign' which overrides repos that require
signed commits; change this to be opt-in by checking a new boolean option (e.g.,
options.noGpgSign) and only push '--no-gpg-sign' when that option is true.
Update the method's options type/interface to include the new flag (default
false), adjust any callers/tests that rely on the old behavior, and ensure the
default behavior preserves signing by not adding the flag unless explicitly
requested.
| async getDefaultBranch(): Promise<string> { | ||
| const currentBranch = await this.getCurrentBranch(); | ||
| const defaultBranches = ['main', 'master', 'develop']; | ||
|
|
||
| if (defaultBranches.includes(currentBranch)) { | ||
| return currentBranch; | ||
| } | ||
|
|
||
| // If not on a default branch, check which default branches exist | ||
| const branches = await this.listBranches(); | ||
| for (const defaultBranch of defaultBranches) { | ||
| if (branches.includes(defaultBranch)) { | ||
| return defaultBranch; | ||
| } | ||
| } | ||
|
|
||
| // Fallback to main | ||
| return 'main'; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
Use remote HEAD to detect default branch (robust).
Current heuristic (“main/master/develop”) is unreliable. Prefer refs/remotes/<remote>/HEAD, then fall back.
- async getDefaultBranch(): Promise<string> {
- const currentBranch = await this.getCurrentBranch();
- const defaultBranches = ['main', 'master', 'develop'];
-
- if (defaultBranches.includes(currentBranch)) {
- return currentBranch;
- }
-
- // If not on a default branch, check which default branches exist
- const branches = await this.listBranches();
- for (const defaultBranch of defaultBranches) {
- if (branches.includes(defaultBranch)) {
- return defaultBranch;
- }
- }
-
- // Fallback to main
- return 'main';
- }
+ async getDefaultBranch(): Promise<string> {
+ // 1) Try origin HEAD
+ try {
+ const ref = (await this.git.raw(['symbolic-ref', '--quiet', '--short', 'refs/remotes/origin/HEAD'])).trim();
+ if (ref) {
+ const parts = ref.split('/');
+ const name = parts.length > 1 ? parts.slice(1).join('/') : ref;
+ if (name) return name;
+ }
+ } catch {}
+ // 2) Fallback to common names present locally
+ const branches = await this.listBranches();
+ if (branches.includes('main')) return 'main';
+ if (branches.includes('master')) return 'master';
+ if (branches.includes('develop')) return 'develop';
+ // 3) Final fallback
+ return 'main';
+ }🏁 Script executed:
#!/bin/bash
# Find any other default-branch utilities to deduplicate with GitAdapter.getDefaultBranch
rg -nP -C2 '\bgetDefaultBranch\s*\('Length of output: 3543
I need to examine the existing implementations to understand the duplication and verify the robustness concern.
I need to check the actual implementations in the codebase to verify the duplication concern and understand the broader context.
🏁 Script executed:
#!/bin/bash
# View git-utils.ts getDefaultBranch implementation
cat packages/tm-core/src/utils/git-utils.ts | sed -n '195,240p'Length of output: 1481
🏁 Script executed:
#!/bin/bash
# Check if git-adapter.ts imports from git-utils
rg -n "import.*git-utils|from.*git-utils" packages/tm-core/src/git/git-adapter.tsLength of output: 0
🏁 Script executed:
#!/bin/bash
# Check the test file to understand what scenarios are being tested
cat packages/tm-core/src/git/git-adapter.test.ts | sed -n '1098,1125p'Length of output: 971
🏁 Script executed:
#!/bin/bash
# View the full git-adapter.ts getDefaultBranch method once more from provided code
cat packages/tm-core/src/git/git-adapter.ts | sed -n '670,695p'Length of output: 703
Robustness concern validated; refactor proposed implementation requires consideration of existing consumers.
The review correctly identifies a robustness gap. The current heuristic in git-adapter.ts is significantly less reliable than checking refs/remotes/origin/HEAD, which is already in use as a fallback in git-utils.ts. However, verification reveals important architectural context:
Key findings:
git-utils.tsimplements a more robust multi-layer fallback (GitHub CLI →git remote show→symbolic-ref), confirming the remote HEAD approach is soundgit-adapter.tsdoesn't delegate togit-utils; it's a separate implementation with different signature (Promise<string>vsPromise<string | null>)- Current tests validate the heuristic approach but won't validate remote HEAD detection after refactoring
- The proposed diff correctly implements the remote HEAD approach with proper error handling
Critical consideration: Changing git-adapter.getDefaultBranch() to potentially return a different value or handle errors differently could impact isDefaultBranch() and callers in preflight-checker.service.ts. Tests should be updated to verify the new approach works as expected and handles edge cases (missing remote HEAD, non-origin remotes).
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Ralph Khreish 35776126+Crunchyman-ralph@users.noreply.github.com# What type of PR is this?
Description
Related Issues
How to Test This
# Example commands or stepsExpected result:
Contributor Checklist
npm run changesetnpm testnpm run format-check(ornpm run formatto fix)Changelog Entry
For Maintainers
Summary by CodeRabbit
New Features
tm autopilotcommands for RED/GREEN/COMMIT cyclesTASK_MASTER_TOOLSenvironment variable (modes: all, core, standard, custom)tm nextcommand for improved task navigationBug Fixes
Documentation