Plan - Implementation Planning

SkillProductivity

Plan implementation for a feature or task. Two modes — fast (single quick plan) or full (richer plan with optional git branch/worktree flow). Use when user says "plan", "new feature", "start feature", "create tasks".

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Plan - Implementation Planning skill

What this skill tells your AI

The instructions your AI receives, as published by thedragoncode/laravel-feeds in .agents/skills/aif-plan/SKILL.md and read by ahel’s review.

Create an implementation plan for a feature or task. Two modes:

  • Fast – quick plan, no git branch, saves to the configured fast plan path (default: .ai-factory/PLAN.md)
  • Full — richer plan, asks preferences, saves to the configured full-plan directory, and optionally creates a git branch/worktree when git is enabled and branch creation is allowed

Workflow

Step 0 (pre): Detect Handoff Mode

Determine Handoff mode, task ID, and branch contract. Resolve each value independently so legacy callers that pass only HANDOFF_MODE and HANDOFF_TASK_ID still enter Handoff mode correctly:

  • HANDOFF_MODE: explicit prompt value if present; otherwise environment value; otherwise empty string.
  • HANDOFF_TASK_ID: explicit prompt value if present; otherwise environment value; otherwise empty string.
  • HANDOFF_BRANCH_PREPARED: explicit prompt value if present; otherwise environment value; otherwise 0.
  • HANDOFF_BRANCH_NAME: explicit prompt value if present; otherwise environment value; otherwise empty string.

Use the Bash tool only for values that were not passed explicitly in the prompt:

Bash: printenv HANDOFF_MODE || true
Bash: printenv HANDOFF_TASK_ID || true
Bash: printenv HANDOFF_BRANCH_PREPARED || true
Bash: printenv HANDOFF_BRANCH_NAME || true

Then check HANDOFF_MODE:

When HANDOFF_MODE is 1 (autonomous Handoff agent)

The Handoff coordinator already manages status transitions and DB writes directly. Do NOT call MCP tools (handoff_sync_status, handoff_push_plan). Instead:

  • No interactive questions: Do not use AskUserQuestion — use sensible defaults (verbose logging, yes to tests, yes to docs, skip roadmap linkage).
  • Mode default: If mode is not specified, default to fast.
  • Plan annotation (MANDATORY): If HANDOFF_TASK_ID is non-empty, you MUST insert <!-- handoff:task:<HANDOFF_TASK_ID> --> as the very first line of the plan file, before the title. This annotation links the plan to its Handoff task for bidirectional sync. Omitting this annotation when HANDOFF_TASK_ID is set is a bug — verify before completing.
Branch ownership under Handoff (CRITICAL)

Handoff owns branch creation at the agent-code level. The skill must NOT create or switch branches when Handoff has prepared one. Apply these rules:

If HANDOFF_BRANCH_PREPARED is 1:

  • Do NOT execute git checkout, git pull, or git checkout -b.
  • Treat --parallel as disabled for all downstream behavior.
  • Do NOT create a worktree.
  • Read HANDOFF_BRANCH_NAME from the prompt / env.
  • Validate strict equality:
    Bash: git rev-parse --abbrev-ref HEAD
    
    The output must equal HANDOFF_BRANCH_NAME exactly. Do not accept partial matches, prefix matches, or "branch contains /" heuristics.
  • If the current branch does not match HANDOFF_BRANCH_NAME, STOP. Report a blocker in the plan summary:

    Branch drift: expected <HANDOFF_BRANCH_NAME>, actual <current>. Do NOT "fix" drift by switching or creating a branch — Handoff classifies that as BranchIsolationError / blocked_external.

  • Use HANDOFF_BRANCH_NAME (with / replaced by -) as the full-mode plan filename stem: <configured plans dir>/<HANDOFF_BRANCH_NAME-with-slashes-replaced>.md. Skip the slug derivation in Step 1.2.

If HANDOFF_MODE is 1 but HANDOFF_BRANCH_PREPARED is unset or 0:

  • Fallback path for older Handoff clients that have not adopted the prepared-branch contract.
  • Execute Step 1.4 branch creation normally per git.create_branches config.
When HANDOFF_MODE is NOT 1 (manual Claude Code session)

If polishing an existing plan, extract the Handoff task ID from the <!-- handoff:task:<id> --> annotation on the first line (if present). If creating a new plan and no annotation context exists, skip all MCP sync — there is no linked Handoff task.

If a task ID IS found in the plan annotation, sync with Handoff via MCP tools:

  • On start: Call handoff_sync_status with { taskId: <extracted-id>, newStatus: "planning", sourceTimestamp: "<current UTC time in ISO 8601 format>", direction: "aif_to_handoff", paused: true }.
  • On completion: Call handoff_push_plan with { taskId: <extracted-id>, planContent: <full plan text> }. Then call handoff_sync_status with { taskId: <extracted-id>, newStatus: "plan_ready", sourceTimestamp: "<current UTC time in ISO 8601 format>", direction: "aif_to_handoff", paused: true }.

CRITICAL: Always pass paused: true with every handoff_sync_status call except done. This prevents the autonomous Handoff agent from picking up the task while you work manually. Only done passes paused: false.

Preserve the <!-- handoff:task:<id> --> annotation on the first line when rewriting the plan file.

Step 0: Load Project Context

FIRST: Read .ai-factory/config.yaml if it exists to resolve:

  • Paths: paths.description, paths.architecture, paths.roadmap, paths.research, paths.rules_file, paths.plan, paths.plans, paths.patches, paths.evolutions, paths.specs, paths.rules, and paths.archive
  • Language: language.ui for AskUserQuestion prompts, language.artifacts for generated plan files, and language.technical_terms for human-readable technical terminology in plan artifacts
  • Git: git.enabled, git.base_branch, git.create_branches, and git.branch_prefix
  • Workflow: workflow.plan_id_format — controls full-mode plan filename shape. Allowed values: slug (default), timestamp, uuid, sequential. Only slug and sequential are active; timestamp and uuid are reserved and currently behave like slug (with an INFO log). The sequential value writes plan files as <NNNN>_<plan_file_stem>.md (see Step 1.2 for the canonical stem and the algorithm). Treat any unknown value as slug and emit WARN [aif-plan] unknown workflow.plan_id_format=<value>; falling back to slug.

If config.yaml doesn't exist, use defaults:

  • Paths: .ai-factory/ for all artifacts
  • ui_language: en
  • artifact_language: en
  • technical_terms_policy: keep
  • Git: enabled: true, base_branch: main, create_branches: true, branch_prefix: feature/
  • Workflow: plan_id_format: slug

Resolved language values:

  • ui_language = language.ui || "en"
  • artifact_language = language.artifacts || language.ui || "en"
  • technical_terms_policy = language.technical_terms || "keep"

If technical_terms_policy is not one of keep, translate, or mixed, treat it as keep. Legacy values such as english also behave like keep.

All AskUserQuestion prompts, progress updates, summaries, and next-step guidance MUST be written in ui_language.

Generated plan artifacts under paths.plan or paths.plans MUST be written in artifact_language.

Templates and examples define structure, not fixed English output. If artifact_language is not en, translate human-readable headings, labels, task prose, roadmap rationale, research summaries, settings explanations, and dependency notes before saving. Preserve markdown structure, checkbox syntax, task IDs, branch names, commit messages, commands, file paths, config keys, package names, API names, WARN/INFO labels, and raw errors unchanged. Apply technical_terms_policy to other human-readable terminology.

Exception: the section heading and body of ## Original Request are fixed raw-source structure and must not be translated, summarized, normalized, or rewritten.

THEN: Read .ai-factory/DESCRIPTION.md (use path from config) if it exists to understand:

  • Tech stack (language, framework, database, ORM)
  • Project architecture
  • Coding conventions
  • Non-functional requirements

ALSO: Read the resolved architecture artifact if it exists (paths.architecture, default: .ai-factory/ARCHITECTURE.md) to understand:

  • Chosen architecture pattern
  • Folder structure conventions
  • Layer/module boundaries
  • Dependency rules

Use this context when:

  • Exploring codebase (know what patterns to look for)
  • Writing task descriptions (use correct technologies)
  • Planning file structure (follow project conventions)
  • Follow architecture guidelines from the resolved architecture artifact when planning file structure and task organization

Read .ai-factory/skill-context/aif-plan/SKILL.md — MANDATORY if the file exists.

This file contains project-specific rules accumulated by $aif-evolve from patches, codebase conventions, and tech-stack analysis. These rules are tailored to the current project.

How to apply skill-context rules:

  • Treat them as project-level overrides for this skill's general instructions
  • When a skill-context rule conflicts with a general rule written in this SKILL.md, the skill-context rule wins (more specific context takes priority — same principle as nested CLAUDE.md files)
  • When there is no conflict, apply both: general rules from SKILL.md + project rules from skill-context
  • Do NOT ignore skill-context rules even if they seem to contradict this skill's defaults — they exist because the project's experience proved the default insufficient
  • CRITICAL: skill-context rules apply to ALL outputs of this skill — including the PLAN.md template and task format. The plan template from TASK-FORMAT.md is a base structure. If a skill-context rule says "tasks MUST include X" or "plan MUST have section Y" — you MUST augment the template accordingly. Generating a plan that violates skill-context rules is a bug.

Enforcement: After generating any output artifact, verify it against all skill-context rules. If any rule is violated — fix the output before presenting it to the user.

OPTIONAL (recommended): Read the resolved roadmap artifact if it exists (paths.roadmap, default: .ai-factory/ROADMAP.md):

  • Use it to link this plan to a specific milestone (when applicable)
  • This reduces ambiguity in $aif-implement milestone completion and $aif-verify roadmap gates

OPTIONAL (recommended): Read the resolved research path if it exists:

  • Treat ## Active Summary (input for $aif-plan) as an additional requirements source
  • Carry over constraints/decisions into tasks and plan settings
  • Prefer the summary over raw notes; use ## Sessions only when you need deeper rationale
  • If the user omitted the feature description, use Active Summary -> Topic: as the default description
  • Track whether research content influenced this plan. Set research_influenced_plan = true only when the Active Summary supplies the default description or when constraints, decisions, goals, open questions, or session rationale from the research artifact shape the plan scope, tasks, settings, or tradeoffs. If the research artifact exists but is stale or unrelated to the user's requested task, leave research_influenced_plan = false, ignore it for plan requirements, and do not add ## Research Context.
  • If any research content influences the plan, the generated plan MUST include ## Research Context with a Source: line pointing to the resolved research artifact and a stable revision marker (Updated: timestamp from the research file plus SHA256: of the copied Active Summary). Omitting this plan-owned research copy is a bug because downstream skills treat the embedded Research Context as the plan's authoritative requirements and use the live research file only for drift checks.
  • Normalize the copied Active Summary before hashing: include exactly the text that will be pasted under ## Research Context after the Source: line, exclude markdown comments and the Source: line itself, preserve line order, trim trailing spaces, use LF line endings, and end with exactly one final newline. Calculate the digest without writing any temporary file or repository artifact: feed the normalized text through stdin / inline shell input to shasum -a 256; if shasum is unavailable, feed the same normalized text to sha256sum. Use the first output field as the SHA256: value.

Step 0.1: Resolve Git State

Do not auto-run git init.

Resolve the current git mode from config first:

  • git.enabled: true → git-aware workflow is allowed
  • git.enabled: false → no-git workflow only
  • git.base_branch → target branch for diffs/merge guidance (default: detected branch or main)
  • git.create_branches: true → full mode may create a branch/worktree
  • git.create_branches: false → full mode still creates a rich plan, but stays on the current branch / repository state

If git.enabled = false:

  • Skip all branch/worktree commands
  • Save full-mode plans under paths.plans/<slug>.md
  • Treat --parallel, --list, and --cleanup as unavailable

If git.enabled = true but the repository is not actually inside a git work tree:

  • Warn the user that git-aware actions are unavailable until the repository is initialized
  • Fall back to the same no-git behavior as above

Step 0.2: Parse Arguments & Select Mode

Extract flags and mode from $ARGUMENTS:

--parallel  → Enable parallel worktree mode (full mode only; requires `git.enabled=true` and `git.create_branches=true`)
--list      → Show all active worktrees, then STOP (git-only)
--cleanup <branch> → Remove worktree and optionally delete branch, then STOP (git-only)
fast        → Fast mode (first word)
full        → Full mode (first word)

Parsing rules:

  • Strip only recognized command tokens in command positions from $ARGUMENTS:
    • fast or full only when used as the leading mode token
    • recognized control flags --parallel, --list, and --cleanup <branch>
    • do not remove matching words inside the user's actual request text
  • Remaining text becomes the description
  • Preserve the remaining text as original_user_request when it is non-empty: trim only outer whitespace introduced by command parsing, but keep internal whitespace, line breaks, wording, casing, and punctuation exactly. This is the user's original planning request and MUST be saved into the plan file later.
  • --list and --cleanup execute immediately and STOP (do NOT continue to Step 1+)
  • If git.enabled = false, reject --parallel, --list, and --cleanup with a short explanation instead of trying git commands
  • If --parallel is set while git.create_branches = false, reject it with a short explanation because parallel mode requires branch creation

If the description is empty:

  • If the resolved research path exists and its Active Summary has a non-empty Topic:, default the description to that topic (no extra user input required) and leave original_user_request empty. Plans created from RESEARCH.md without an explicit user request MUST NOT include an Original Request section.
  • Otherwise, ask the user for a short feature description. Preserve the user's answer verbatim as original_user_request and save it into the plan file later.

Original request contract:

  • If the user explicitly supplied a planning request (for example $aif-plan ТУТ ЗАПРОС НА ПЛАН, $aif-plan full ТУТ ЗАПРОС НА ПЛАН, or an answer to the description prompt), the generated plan MUST include ## Original Request.
  • ## Original Request contains the exact user-provided request text after only recognized command tokens are removed and only outer whitespace is trimmed. Do not rewrite, summarize, translate, or normalize its wording, even when artifact_language differs.
  • If the description was derived only from RESEARCH.md because the user did not provide a request, omit ## Original Request; the committed source is ## Research Context instead.
  • If the user supplied a request and RESEARCH.md also influenced the plan, include both ## Original Request and ## Research Context.

If --list is present, jump to --list Subcommand. If --cleanup is present, jump to --cleanup Subcommand.

Mode selection:

  • fast keyword → fast mode
  • full keyword → full mode
  • Neither → ask interactively:
AskUserQuestion: Which planning mode?

Options:
1. Full (Recommended) — richer plan, asks preferences, optional branch/worktree flow when git settings allow it
2. Fast – quick plan, no branch, saves to the resolved fast plan path

If the user did not provide a description and the resolved research path exists:

  • Mention that you will default the description to the Active Summary topic
  • Only ask for full vs fast (no description prompt needed)

For concrete parsing examples and expected behavior per command shape, read references/EXAMPLES.md (Argument Parsing).


Full Mode

Step 1: Parse Description & Quick Reconnaissance

From the description, extract:

  • Core functionality being added
  • Key domain terms
  • Type (feature, enhancement, fix, refactor)

Use Task tool with subagent_type: Explore to quickly understand the relevant parts of the codebase. This runs as a subagent and keeps the main context clean.

Based on the parsed description, launch 1-2 Explore agents in parallel:

Task(subagent_type: Explore, model: sonnet, prompt:
  "In [project root], find files and modules related to [feature domain keywords].
   Report: key directories, relevant files, existing patterns, integration points.
   Thoroughness: quick. Be concise — return a structured summary, not file contents.")

Rules:

  • 1-2 agents max, "quick" thoroughness — this is reconnaissance, not deep analysis
  • Deep exploration happens later in Step 3
  • If .ai-factory/DESCRIPTION.md already provides sufficient context, this step can be skipped

Step 1.2: Generate Full-Mode Plan Identifier

This step produces two distinct values:

  • branch_name — the git branch (only when git.enabled = true and git.create_branches = true)
  • plan_file_stem — the filename stem under <configured plans dir>/ (with or without a NNNN_ prefix)

Both are derived in a fixed order so the producer here and the branch-based consumers in $aif-implement / $aif-improve / $aif-verify / $aif-rules-check always agree on the filename.

1.2.a — Resolve the canonical plan_file_stem

Pick the first matching case:

  1. HANDOFF_BRANCH_PREPARED = 1plan_file_stem = HANDOFF_BRANCH_NAME with every / replaced by -. Skip slug generation entirely. No branch_name is created here (Handoff already owns the branch).
  2. git.enabled = true AND git.create_branches = true → generate a description slug, then branch_name = <git.branch_prefix><slug> (default prefix: feature/). Set plan_file_stem = branch_name with every / replaced by - (for example feature-user-authentication).
  3. Otherwise (git.enabled = false OR git.create_branches = false) → plan_file_stem = <description slug>. No branch_name is created.

Slug rules (cases 2 and 3):

  • Lowercase, hyphen-separated, max 50 characters
  • No special characters except hyphens
  • Descriptive but concise

Branch examples (case 2):

  • feature/user-authentication
  • fix/cart-total-calculation
  • refactor/api-error-handling
  • chore/upgrade-dependencies

Invariant: branch-based consumer skills compute their lookup stem as current-branch-with-slashes-replaced. Cases 1 and 2 above already match that. Case 3 never has a branch, so consumers fall back to the lone full-mode plan in <configured plans dir>/ (see aif-implement Step 0.2). Producing a plan_file_stem outside these rules breaks discovery.

1.2.b — Apply the workflow.plan_id_format prefix

Default: no prefix. The plan filename is <configured plans dir>/<plan_file_stem>.md.

Format-specific handling:

  • slug (default) → no prefix.
  • timestamp / uuidreserved values; treat as slug for now. Emit INFO [aif-plan] workflow.plan_id_format=<value> is reserved and behaves like slug; numbering is not applied. Do NOT invent a stem shape — branch-based consumers do not know how to discover non-sequential prefixes.
  • Unknown values → already handled in Step 0: emit WARN [aif-plan] unknown workflow.plan_id_format=<value>; falling back to slug. Behaves like slug here.
  • sequential → apply the algorithm in 1.2.c.

Sequential is force-disabled when HANDOFF_BRANCH_PREPARED = 1. In that case keep the bare plan_file_stem and emit INFO [aif-plan] sequential numbering disabled under HANDOFF_BRANCH_PREPARED=1.

1.2.c — Sequential numbering algorithm

Prepend a 4-digit numeric prefix to plan_file_stem. The prefix is computed from existing numbered plans in <configured plans dir>. The branch name (when one exists) stays unchanged so existing git tooling, CI, and PR conventions are unaffected.

1. Find existing numbered plans in <configured plans dir>:
     Glob: <configured plans dir>/[0-9][0-9][0-9][0-9]_*.md
2. Parse the leading 4 digits from each match into an integer.
   Filter out names that do not match ^[0-9]{4}_.+\.md$.
3. If any matches exist:
     max_existing = max(prefixes)
     If max_existing >= 9999:
       ABORT with error:
         "sequential cap reached: a plan numbered 9999 already exists in <configured plans dir>."
         "Switch workflow.plan_id_format back to slug, or move the 9999-numbered file out of the directory (note: doing so will free 9999 for the next plan to reuse)."
     next = max_existing + 1
   Else:
     next = 1
4. prefix = zero-padded 4-digit string of next   (e.g. 1 → "0001", 42 → "0042")
5. Final plan file path:
     <configured plans dir>/<prefix>_<plan_file_stem>.md

Implementation notes:

  • Use Glob only to enumerate existing numbered plans. Do NOT shell out to lsaif-plan's frontmatter does not grant Bash(ls *), so the ls path would fail in production.
  • The 4-digit [0-9][0-9][0-9][0-9] glob is strict by contract: the format supports 0001..9999 only. The error in step 3 enforces this.
  • --parallel scope (TL;DR — source-worktree scoped):
    • Where the prefix is computed: the source worktree's <configured plans dir> (the repo where $aif-plan was invoked) — i.e. exactly here, in Step 1.2.c.
    • When it is computed: before the optional cd <WORKTREE> in Step 1.4.
    • Where the plan file is written: the same relative <configured plans dir>/<NNNN>_<plan_file_stem>.md path inside the target worktree, so the prefix and destination directory stay consistent.
    • What you must NOT do: never recompute the prefix from the target worktree's plans dir after cd <WORKTREE>. The target dir is typically empty and would re-allocate 0001 on every parallel run, breaking the cross-worktree numbering contract on merge.

Rules:

  • Numbering is derived from existing files in <configured plans dir>. Deleting or moving a numbered plan out of the directory can free that number for reuse on the next run — keep plans in place if you rely on stable cross-references.
  • Archived plans are excluded from numbering. Plans moved to paths.archive/plans/ by $aif-archive are not in <configured plans dir> and therefore not counted. Archiving the highest-numbered plan frees that number for reuse.
  • Numbering is bounded — 9999 is a hard cap; the algorithm errors instead of writing 10000_… so consumer globs (also 4-digit) cannot drift out of contract.
  • The prefix lives only on the plan file. The git branch (when present) stays <branch_prefix><slug> without a number.
  • This setting is ignored for fast plans (paths.plan is a single file) and fix plans (paths.fix_plan is a single file).

Logging: INFO [aif-plan] resolved plan file: <path> (format=<value>).

Step 1.3: Ask About Preferences

IMPORTANT: Always ask the user before proceeding:

AskUserQuestion: Before we start, a few questions:

1. Should I write tests for this feature?
   a. Yes, write tests
   b. No, skip tests

Shortened here. Read the whole file on GitHub.

Signals

GitHub stars
36
Forks
1
Last commit
Sep 2026

ahel recommends instead

Advanced
Catalog kind
skill
Gateway key
aif-plan-thedragoncode
Source
github.com/thedragoncode/laravel-feeds