Add insert command
This commit is contained in:
34
specs/005-add-insert-command/checklists/requirements.md
Normal file
34
specs/005-add-insert-command/checklists/requirements.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# Specification Quality Checklist: Insert Command for Positional Text Injection
|
||||
|
||||
**Purpose**: Validate specification completeness and quality before proceeding to planning
|
||||
**Created**: 2025-10-30
|
||||
**Feature**: specs/001-add-insert-command/spec.md
|
||||
|
||||
## Content Quality
|
||||
|
||||
- [X] No implementation details (languages, frameworks, APIs)
|
||||
- [X] Focused on user value and business needs
|
||||
- [X] Written for non-technical stakeholders
|
||||
- [X] All mandatory sections completed
|
||||
|
||||
## Requirement Completeness
|
||||
|
||||
- [X] No [NEEDS CLARIFICATION] markers remain
|
||||
- [X] Requirements are testable and unambiguous
|
||||
- [X] Success criteria are measurable
|
||||
- [X] Success criteria are technology-agnostic (no implementation details)
|
||||
- [X] All acceptance scenarios are defined
|
||||
- [X] Edge cases are identified
|
||||
- [X] Scope is clearly bounded
|
||||
- [X] Dependencies and assumptions identified
|
||||
|
||||
## Feature Readiness
|
||||
|
||||
- [X] All functional requirements have clear acceptance criteria
|
||||
- [X] User scenarios cover primary flows
|
||||
- [X] Feature meets measurable outcomes defined in Success Criteria
|
||||
- [X] No implementation details leak into specification
|
||||
|
||||
## Notes
|
||||
|
||||
- Items marked incomplete require spec updates before `/speckit.clarify` or `/speckit.plan`
|
||||
243
specs/005-add-insert-command/contracts/insert-command.yaml
Normal file
243
specs/005-add-insert-command/contracts/insert-command.yaml
Normal file
@@ -0,0 +1,243 @@
|
||||
openapi: 3.1.0
|
||||
info:
|
||||
title: Renamer Insert Command API
|
||||
version: 0.1.0
|
||||
description: >
|
||||
Contract representation of the `renamer insert` command workflows (preview/apply/undo)
|
||||
for automation harnesses and documentation parity.
|
||||
servers:
|
||||
- url: cli://renamer
|
||||
description: Command-line invocation surface
|
||||
paths:
|
||||
/insert/preview:
|
||||
post:
|
||||
summary: Preview insert operations
|
||||
description: Mirrors `renamer insert <position> <text> --dry-run`
|
||||
operationId: previewInsert
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/InsertRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: Successful preview
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/InsertPreview'
|
||||
'400':
|
||||
description: Validation error (invalid position, empty text, etc.)
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
/insert/apply:
|
||||
post:
|
||||
summary: Apply insert operations
|
||||
description: Mirrors `renamer insert <position> <text> --yes`
|
||||
operationId: applyInsert
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/InsertRequest'
|
||||
- type: object
|
||||
properties:
|
||||
dryRun:
|
||||
type: boolean
|
||||
const: false
|
||||
responses:
|
||||
'200':
|
||||
description: Apply succeeded
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/InsertApplyResult'
|
||||
'409':
|
||||
description: Conflict detected (duplicate targets, existing files)
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ConflictResponse'
|
||||
'400':
|
||||
description: Validation error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
/insert/undo:
|
||||
post:
|
||||
summary: Undo latest insert batch
|
||||
description: Mirrors `renamer undo` when last ledger entry corresponds to insert command.
|
||||
operationId: undoInsert
|
||||
responses:
|
||||
'200':
|
||||
description: Undo succeeded
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/UndoResult'
|
||||
'409':
|
||||
description: Ledger inconsistent or no insert entry found
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
components:
|
||||
schemas:
|
||||
InsertRequest:
|
||||
type: object
|
||||
required:
|
||||
- workingDir
|
||||
- positionToken
|
||||
- insertText
|
||||
properties:
|
||||
workingDir:
|
||||
type: string
|
||||
positionToken:
|
||||
type: string
|
||||
description: '^', '$', positive integer, or negative integer.
|
||||
insertText:
|
||||
type: string
|
||||
description: Unicode string to insert (must not contain path separators).
|
||||
includeDirs:
|
||||
type: boolean
|
||||
default: false
|
||||
recursive:
|
||||
type: boolean
|
||||
default: false
|
||||
includeHidden:
|
||||
type: boolean
|
||||
default: false
|
||||
extensionFilter:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
dryRun:
|
||||
type: boolean
|
||||
default: true
|
||||
autoConfirm:
|
||||
type: boolean
|
||||
default: false
|
||||
InsertPreview:
|
||||
type: object
|
||||
required:
|
||||
- totalCandidates
|
||||
- totalChanged
|
||||
- noChange
|
||||
- entries
|
||||
properties:
|
||||
totalCandidates:
|
||||
type: integer
|
||||
minimum: 0
|
||||
totalChanged:
|
||||
type: integer
|
||||
minimum: 0
|
||||
noChange:
|
||||
type: integer
|
||||
minimum: 0
|
||||
conflicts:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Conflict'
|
||||
warnings:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
entries:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/PreviewEntry'
|
||||
InsertApplyResult:
|
||||
type: object
|
||||
required:
|
||||
- totalApplied
|
||||
- noChange
|
||||
- ledgerEntryId
|
||||
properties:
|
||||
totalApplied:
|
||||
type: integer
|
||||
minimum: 0
|
||||
noChange:
|
||||
type: integer
|
||||
minimum: 0
|
||||
ledgerEntryId:
|
||||
type: string
|
||||
warnings:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
UndoResult:
|
||||
type: object
|
||||
required:
|
||||
- restored
|
||||
- ledgerEntryId
|
||||
properties:
|
||||
restored:
|
||||
type: integer
|
||||
ledgerEntryId:
|
||||
type: string
|
||||
message:
|
||||
type: string
|
||||
Conflict:
|
||||
type: object
|
||||
required:
|
||||
- originalPath
|
||||
- proposedPath
|
||||
- reason
|
||||
properties:
|
||||
originalPath:
|
||||
type: string
|
||||
proposedPath:
|
||||
type: string
|
||||
reason:
|
||||
type: string
|
||||
enum:
|
||||
- duplicate_target
|
||||
- invalid_position
|
||||
- existing_file
|
||||
- existing_directory
|
||||
PreviewEntry:
|
||||
type: object
|
||||
required:
|
||||
- originalPath
|
||||
- proposedPath
|
||||
- status
|
||||
properties:
|
||||
originalPath:
|
||||
type: string
|
||||
proposedPath:
|
||||
type: string
|
||||
status:
|
||||
type: string
|
||||
enum:
|
||||
- changed
|
||||
- no_change
|
||||
- skipped
|
||||
insertedText:
|
||||
type: string
|
||||
ErrorResponse:
|
||||
type: object
|
||||
required:
|
||||
- error
|
||||
properties:
|
||||
error:
|
||||
type: string
|
||||
remediation:
|
||||
type: string
|
||||
ConflictResponse:
|
||||
type: object
|
||||
required:
|
||||
- error
|
||||
- conflicts
|
||||
properties:
|
||||
error:
|
||||
type: string
|
||||
conflicts:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Conflict'
|
||||
62
specs/005-add-insert-command/data-model.md
Normal file
62
specs/005-add-insert-command/data-model.md
Normal file
@@ -0,0 +1,62 @@
|
||||
# Data Model – Insert Command
|
||||
|
||||
## Entity: InsertRequest
|
||||
- **Fields**
|
||||
- `WorkingDir string` — Absolute path derived from CLI `--path` or current directory.
|
||||
- `PositionToken string` — Raw user input (`^`, `$`, positive int, negative int) describing insertion point.
|
||||
- `InsertText string` — Unicode string to insert.
|
||||
- `IncludeDirs bool` — Mirrors `--include-dirs` scope flag.
|
||||
- `Recursive bool` — Mirrors `--recursive`.
|
||||
- `IncludeHidden bool` — True only when `--hidden` supplied.
|
||||
- `ExtensionFilter []string` — Normalized tokens from `--extensions`.
|
||||
- `DryRun bool` — Preview-only execution state.
|
||||
- `AutoConfirm bool` — Captures `--yes` for non-interactive runs.
|
||||
- `Timestamp time.Time` — Invocation timestamp for ledger correlation.
|
||||
- **Validation Rules**
|
||||
- Reject empty `InsertText`, path separators, or control characters.
|
||||
- Ensure `PositionToken` parses to a valid rune index relative to the stem (`^` = 0, `$` = len, positive 1-based, negative = offset from end).
|
||||
- Confirm resulting filename is non-empty and does not change extension semantics.
|
||||
- **Relationships**
|
||||
- Consumed by insert engine to plan operations.
|
||||
- Serialized into ledger metadata with `InsertSummary`.
|
||||
|
||||
## Entity: InsertSummary
|
||||
- **Fields**
|
||||
- `TotalCandidates int` — Items inspected after scope filtering.
|
||||
- `TotalChanged int` — Entries that will change after insertion.
|
||||
- `NoChange int` — Entries already containing target string at position (if applicable).
|
||||
- `Conflicts []Conflict` — Target collisions or invalid positions.
|
||||
- `Warnings []string` — Validation notices (duplicates, trimmed tokens, skipped hidden items).
|
||||
- `Entries []PreviewEntry` — Ordered original/proposed mappings with status.
|
||||
- `LedgerMetadata map[string]any` — Snapshot persisted with ledger entry (position, text, scope flags).
|
||||
- **Validation Rules**
|
||||
- Conflicts must be empty before apply.
|
||||
- `TotalChanged + NoChange` equals count of entries with status `changed` or `no_change`.
|
||||
- Entries sorted deterministically by original path.
|
||||
- **Relationships**
|
||||
- Emitted to preview renderer and ledger writer.
|
||||
- Input for undo verification.
|
||||
|
||||
## Entity: Conflict
|
||||
- **Fields**
|
||||
- `OriginalPath string`
|
||||
- `ProposedPath string`
|
||||
- `Reason string` — (`duplicate_target`, `invalid_position`, `existing_file`, etc.)
|
||||
- **Validation Rules**
|
||||
- `ProposedPath` unique among planned operations.
|
||||
- Reason restricted to known enum values for messaging.
|
||||
- **Relationships**
|
||||
- Reported in preview output and used to block apply.
|
||||
|
||||
## Entity: PreviewEntry
|
||||
- **Fields**
|
||||
- `OriginalPath string`
|
||||
- `ProposedPath string`
|
||||
- `Status string` — `changed`, `no_change`, `skipped`.
|
||||
- `InsertedText string` — Text segment inserted (for auditing).
|
||||
- **Validation Rules**
|
||||
- `ProposedPath` equals `OriginalPath` when `Status == "no_change"`.
|
||||
- `InsertedText` empty only for `no_change` or `skipped`.
|
||||
- **Relationships**
|
||||
- Displayed in preview output.
|
||||
- Persisted with ledger metadata for undo playback.
|
||||
105
specs/005-add-insert-command/plan.md
Normal file
105
specs/005-add-insert-command/plan.md
Normal file
@@ -0,0 +1,105 @@
|
||||
# Implementation Plan: Insert Command for Positional Text Injection
|
||||
|
||||
**Branch**: `005-add-insert-command` | **Date**: 2025-10-30 | **Spec**: `specs/005-add-insert-command/spec.md`
|
||||
**Input**: Feature specification from `/specs/005-add-insert-command/spec.md`
|
||||
|
||||
**Note**: This template is filled in by the `/speckit.plan` command. See `.specify/templates/commands/plan.md` for the execution workflow.
|
||||
|
||||
## Summary
|
||||
|
||||
Deliver a `renamer insert` subcommand that inserts a specified string into filenames at designated positions (start, end, absolute, relative) with Unicode-aware behavior, deterministic previews, ledger-backed undo, and automation-friendly outputs.
|
||||
|
||||
## Technical Context
|
||||
|
||||
<!--
|
||||
ACTION REQUIRED: Replace the content in this section with the technical details
|
||||
for the project. The structure here is presented in advisory capacity to guide
|
||||
the iteration process.
|
||||
-->
|
||||
|
||||
**Language/Version**: Go 1.24
|
||||
**Primary Dependencies**: `spf13/cobra`, `spf13/pflag`, internal traversal/history/output packages
|
||||
**Storage**: Local filesystem + `.renamer` ledger files
|
||||
**Testing**: `go test ./...`, contract + integration suites under `tests/`, new smoke script
|
||||
**Target Platform**: Cross-platform CLI (Linux, macOS, Windows shells)
|
||||
**Project Type**: Single CLI project (`cmd/`, `internal/`, `tests/`, `scripts/`)
|
||||
**Performance Goals**: 500-file insert (preview+apply) completes in <2 minutes end-to-end
|
||||
**Constraints**: Unicode-aware insertion points, preview-first safety, ledger reversibility
|
||||
**Scale/Scope**: Operates on thousands of filesystem entries per invocation within local directories
|
||||
|
||||
## Constitution Check
|
||||
|
||||
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
|
||||
|
||||
- Preview flow MUST show deterministic rename mappings and require explicit confirmation (Preview-First Safety). ✅ Extend insert preview to render original → proposed names with highlighted insertion segments before any apply.
|
||||
- Undo strategy MUST describe how the `.renamer` ledger entry is written and reversed (Persistent Undo Ledger). ✅ Reuse ledger append with metadata (position token, inserted string) and ensure `undo` replays operations safely.
|
||||
- Planned rename rules MUST document their inputs, validations, and composing order (Composable Rule Engine). ✅ Define an insert rule consuming position tokens, performing Unicode-aware slicing, and integrating with existing traversal pipeline.
|
||||
- Scope handling MUST cover files vs directories (`-d`), recursion (`-r`), and extension filtering via `-e` without escaping the requested path (Scope-Aware Traversal). ✅ Leverage shared listing/traversal flags so insert respects scope filters and hidden/default exclusions.
|
||||
- CLI UX plan MUST confirm Cobra usage, flag naming, help text, and automated tests for preview/undo flows (Ergonomic CLI Stewardship). ✅ Add Cobra subcommand with consistent flags, help examples, contract/integration tests, and smoke coverage.
|
||||
|
||||
*Post-Design Verification (2025-10-30): Research and design artifacts document preview behavior, ledger metadata, Unicode-aware positions, and CLI UX updates — no gate violations detected.*
|
||||
|
||||
## Project Structure
|
||||
|
||||
### Documentation (this feature)
|
||||
|
||||
```text
|
||||
specs/[###-feature]/
|
||||
├── plan.md # This file (/speckit.plan command output)
|
||||
├── research.md # Phase 0 output (/speckit.plan command)
|
||||
├── data-model.md # Phase 1 output (/speckit.plan command)
|
||||
├── quickstart.md # Phase 1 output (/speckit.plan command)
|
||||
├── contracts/ # Phase 1 output (/speckit.plan command)
|
||||
└── tasks.md # Phase 2 output (/speckit.tasks command - NOT created by /speckit.plan)
|
||||
```
|
||||
|
||||
### Source Code (repository root)
|
||||
<!--
|
||||
ACTION REQUIRED: Replace the placeholder tree below with the concrete layout
|
||||
for this feature. Delete unused options and expand the chosen structure with
|
||||
real paths (e.g., apps/admin, packages/something). The delivered plan must
|
||||
not include Option labels.
|
||||
-->
|
||||
|
||||
```text
|
||||
cmd/
|
||||
├── root.go
|
||||
├── list.go
|
||||
├── replace.go
|
||||
├── remove.go
|
||||
├── extension.go
|
||||
└── undo.go
|
||||
|
||||
internal/
|
||||
├── filters/
|
||||
├── history/
|
||||
├── listing/
|
||||
├── output/
|
||||
├── remove/
|
||||
├── replace/
|
||||
├── extension/
|
||||
└── traversal/
|
||||
|
||||
tests/
|
||||
├── contract/
|
||||
├── integration/
|
||||
├── fixtures/
|
||||
└── unit/
|
||||
|
||||
scripts/
|
||||
├── smoke-test-list.sh
|
||||
├── smoke-test-replace.sh
|
||||
├── smoke-test-remove.sh
|
||||
└── smoke-test-extension.sh
|
||||
```
|
||||
|
||||
**Structure Decision**: Extend the single CLI project by adding new `cmd/insert.go`, `internal/insert/` package, contract/integration coverage under existing `tests/` hierarchy, and an insert smoke script alongside other command scripts.
|
||||
|
||||
## Complexity Tracking
|
||||
|
||||
> **Fill ONLY if Constitution Check has violations that must be justified**
|
||||
|
||||
| Violation | Why Needed | Simpler Alternative Rejected Because |
|
||||
|-----------|------------|-------------------------------------|
|
||||
| [e.g., 4th project] | [current need] | [why 3 projects insufficient] |
|
||||
| [e.g., Repository pattern] | [specific problem] | [why direct DB access insufficient] |
|
||||
28
specs/005-add-insert-command/quickstart.md
Normal file
28
specs/005-add-insert-command/quickstart.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# Quickstart – Insert Command
|
||||
|
||||
1. **Preview an insertion at the start of filenames.**
|
||||
```bash
|
||||
renamer insert ^ "[2025] " --dry-run
|
||||
```
|
||||
- Shows the prepended tag without applying changes.
|
||||
- Useful for tagging archival folders.
|
||||
|
||||
2. **Insert text near the end while preserving extensions.**
|
||||
```bash
|
||||
renamer insert -1 "_FINAL" --path ./reports --dry-run
|
||||
```
|
||||
- `-1` places the string before the last character of the stem.
|
||||
- Combine with `--extensions` to limit to specific file types.
|
||||
|
||||
3. **Commit changes once preview looks correct.**
|
||||
```bash
|
||||
renamer insert 3 "_QA" --yes --path ./builds
|
||||
```
|
||||
- `--yes` auto-confirms using the last preview.
|
||||
- Ledger entry records the position token and inserted text.
|
||||
|
||||
4. **Undo the most recent insert batch if needed.**
|
||||
```bash
|
||||
renamer undo --path ./builds
|
||||
```
|
||||
- Restores original names using ledger metadata.
|
||||
17
specs/005-add-insert-command/research.md
Normal file
17
specs/005-add-insert-command/research.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# Phase 0 Research – Insert Command
|
||||
|
||||
## Decision: Reuse Traversal + Preview Pipeline for Insert Rule
|
||||
- **Rationale**: Existing replace/remove/extension commands share a scope walker and preview formatter that already respect `--path`, `-r`, `--hidden`, and ledger logging. Extending this infrastructure minimizes duplication and guarantees constitutional compliance for preview-first safety.
|
||||
- **Alternatives considered**: Building a standalone walker for insert was rejected because it risks divergence in conflict handling and ledger metadata. Hooking insert into `replace` internals was rejected to keep rule responsibilities separated.
|
||||
|
||||
## Decision: Interpret Positions Using Unicode Code Points on Filename Stems
|
||||
- **Rationale**: Go’s rune indexing treats each Unicode code point as one element, aligning with user expectations for multilingual filenames. Operating on the stem (excluding extension) keeps behavior consistent with common batch-renaming tools.
|
||||
- **Alternatives considered**: Byte-based offsets were rejected because multi-byte characters would break user expectations. Treating the full filename including extension was rejected to avoid forcing users to re-add extensions manually.
|
||||
|
||||
## Decision: Ledger Metadata Includes Position Token and Inserted Text
|
||||
- **Rationale**: Storing the position directive (`^`, `$`, positive, negative) and inserted string enables precise undo, auditing, and potential future analytics. This mirrors how replace/remove log the rule context.
|
||||
- **Alternatives considered**: Logging only before/after paths was rejected because it obscures the applied rule and complicates debugging automated runs.
|
||||
|
||||
## Decision: Block Apply on Duplicate Targets or Invalid Positions
|
||||
- **Rationale**: Preventing collisions and out-of-range indices prior to file mutations preserves data integrity and meets preview-first guarantees. Existing conflict detection helpers can be adapted for insert.
|
||||
- **Alternatives considered**: Allowing apply with overwrites was rejected due to high data-loss risk. Auto-truncating positions was rejected because silent fallback leads to inconsistent results across files.
|
||||
101
specs/005-add-insert-command/spec.md
Normal file
101
specs/005-add-insert-command/spec.md
Normal file
@@ -0,0 +1,101 @@
|
||||
# Feature Specification: Insert Command for Positional Text Injection
|
||||
|
||||
**Feature Branch**: `005-add-insert-command`
|
||||
**Created**: 2025-10-30
|
||||
**Status**: Draft
|
||||
**Input**: User description: "实现插入(Insert)字符,支持在文件名指定位置插入指定字符串数据,示例 renamer insert <position> <string>, position:可以包含 ^:开头、$:结尾、 正数:字符位置、负数:倒数字符位置。!!重要:需要考虑中文字符"
|
||||
|
||||
## User Scenarios & Testing *(mandatory)*
|
||||
|
||||
### User Story 1 - Insert Text at Target Position (Priority: P1)
|
||||
|
||||
As a power user organizing media files, I want to insert a label into filenames at a specific character position so that I can batch-tag assets without manually editing each name.
|
||||
|
||||
**Why this priority**: Provides the core value proposition of the insert command—precise, repeatable filename updates that accelerate organization tasks.
|
||||
|
||||
**Independent Test**: In a sample directory with mixed filenames, run `renamer insert 3 _tag --dry-run` and verify the preview shows the marker inserted at the third character (Unicode-aware). Re-run with `--yes` to confirm filesystem changes and ledger entry.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** files named `项目A报告.docx` and `项目B报告.docx`, **When** the user runs `renamer insert ^ 2025- --dry-run`, **Then** the preview lists `2025-项目A报告.docx` and `2025-项目B报告.docx`.
|
||||
2. **Given** files named `holiday.jpg` and `trip.jpg`, **When** the user runs `renamer insert -1 X --yes`, **Then** the apply step inserts `X` before the last character of each base name while preserving extensions.
|
||||
|
||||
---
|
||||
|
||||
### User Story 2 - Automation-Friendly Batch Inserts (Priority: P2)
|
||||
|
||||
As an operator running renamer in CI, I need deterministic exit codes, ledger metadata, and undo support when inserting text so scripted jobs remain auditable and reversible.
|
||||
|
||||
**Why this priority**: Ensures production and automation workflows can rely on the new command without risking data loss.
|
||||
|
||||
**Independent Test**: Execute `renamer insert $ _ARCHIVE --yes --path ./fixtures`, verify exit code `0`, inspect the latest `.renamer` entry for recorded positions/string, then run `renamer undo` to restore originals.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** a non-interactive run with `--yes`, **When** insertion completes without conflicts, **Then** exit code is `0` and the ledger stores the original names, position rule, inserted text, and timestamps.
|
||||
2. **Given** a ledger entry produced by `renamer insert`, **When** `renamer undo` executes, **Then** all affected files revert to their exact previous names even across locales.
|
||||
|
||||
---
|
||||
|
||||
### User Story 3 - Validate Positions and Multilingual Inputs (Priority: P3)
|
||||
|
||||
As a user preparing filenames with multilingual characters, I want validation and preview warnings for invalid positions, overlapping results, or unsupported encodings so I can adjust commands before committing changes.
|
||||
|
||||
**Why this priority**: Protects against data corruption, especially with Unicode characters where byte counts differ from visible characters.
|
||||
|
||||
**Independent Test**: Run `renamer insert 50 _X --dry-run` on files shorter than 50 code points and confirm the command exits with a non-zero status explaining the out-of-range index; validate that Chinese filenames are handled correctly in previews.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** an index larger than the filename length, **When** the command runs, **Then** it fails with a descriptive error and no filesystem changes occur.
|
||||
2. **Given** an insertion that would create duplicate names, **When** preview executes, **Then** conflicts are surfaced and apply is blocked until resolved.
|
||||
|
||||
---
|
||||
|
||||
### Edge Cases
|
||||
|
||||
- What happens when the requested position is outside the filename length (positive or negative)?
|
||||
- How are filenames handled when inserting before/after Unicode characters or surrogate pairs?
|
||||
- How are directories or hidden files treated when `--include-dirs` or `--hidden` is omitted or provided?
|
||||
- What feedback is provided when insertion would produce duplicate targets or empty names?
|
||||
- How does the command behave when the inserted string is empty, whitespace-only, or contains path separators?
|
||||
- What occurs when multiple files differ only by case and the insert results in conflicting targets on case-insensitive filesystems?
|
||||
|
||||
## Requirements *(mandatory)*
|
||||
|
||||
### Functional Requirements
|
||||
|
||||
- **FR-001**: CLI MUST provide a dedicated `insert` subcommand accepting a positional argument (`^`, `$`, positive integer, or negative integer) and the string to insert.
|
||||
- **FR-002**: Insert positions MUST be interpreted using Unicode code points on the filename stem (excluding extension) so multi-byte characters count as a single position.
|
||||
- **FR-003**: The command MUST support scope flags (`--path`, `--recursive`, `--include-dirs`, `--hidden`, `--extensions`, `--dry-run`, `--yes`) consistent with existing commands.
|
||||
- **FR-004**: Preview MUST display original and proposed names, highlighting inserted segments, and block apply when conflicts or invalid positions are detected.
|
||||
- **FR-005**: Apply MUST update filesystem entries atomically per batch, record operations in the `.renamer` ledger with inserted string, position rule, affected files, and timestamps, and support undo.
|
||||
- **FR-006**: Validation MUST reject positions outside the allowable range, empty insertion strings (unless explicitly allowed), and inputs containing path separators or control characters.
|
||||
- **FR-007**: Help output MUST describe position semantics (`^`, `$`, positive, negative), Unicode handling, and examples for both files and directories.
|
||||
- **FR-008**: Automation runs with `--yes` MUST emit deterministic exit codes (`0` success, non-zero on validation/conflicts) and human-readable messages that can be parsed for errors.
|
||||
|
||||
### Key Entities
|
||||
|
||||
- **InsertRequest**: Working directory, position token, insertion string, scope flags, dry-run/apply mode.
|
||||
- **InsertSummary**: Counts of processed items, per-position match details, conflicts, warnings, and preview entries with status (`changed`, `no_change`, `skipped`).
|
||||
|
||||
## Success Criteria *(mandatory)*
|
||||
|
||||
### Measurable Outcomes
|
||||
|
||||
- **SC-001**: Users insert text into 500 filenames (preview + apply) in under 2 minutes end-to-end.
|
||||
- **SC-002**: 95% of beta users correctly apply a positional insert after reading `renamer insert --help` without additional guidance.
|
||||
- **SC-003**: Automated regression tests confirm insert + undo cycles leave the filesystem unchanged in 100% of scripted scenarios.
|
||||
- **SC-004**: Support tickets related to manual filename labeling drop by 30% within the first release cycle post-launch.
|
||||
|
||||
## Assumptions
|
||||
|
||||
- Positions operate on the filename stem (path excluded, extension preserved); inserting at `$` occurs immediately before the extension dot when present.
|
||||
- Empty insertion strings are treated as invalid to avoid silent no-ops; users must provide at least one visible character.
|
||||
- Unicode normalization is assumed to be NFC; filenames are treated as sequences of Unicode code points using the runtime’s native string handling.
|
||||
|
||||
## Dependencies & Risks
|
||||
|
||||
- Requires reuse and possible extension of existing traversal, preview, and ledger infrastructure to accommodate positional operations.
|
||||
- Accurate Unicode handling depends on the runtime’s Unicode utilities; additional testing may be necessary for combining marks and surrogate pairs.
|
||||
- Insertion near filesystem path separators must be restricted to avoid creating invalid paths or escape sequences.
|
||||
158
specs/005-add-insert-command/tasks.md
Normal file
158
specs/005-add-insert-command/tasks.md
Normal file
@@ -0,0 +1,158 @@
|
||||
# Tasks: Insert Command for Positional Text Injection
|
||||
|
||||
**Input**: Design documents from `/specs/005-add-insert-command/`
|
||||
**Prerequisites**: plan.md (required), spec.md (required), research.md, data-model.md, contracts/
|
||||
|
||||
**Tests**: Contract and integration tests included per spec emphasis on preview determinism, ledger integrity, and Unicode handling.
|
||||
|
||||
**Organization**: Tasks are grouped by user story to enable independent implementation and testing of each story.
|
||||
|
||||
## Phase 1: Setup (Shared Infrastructure)
|
||||
|
||||
**Purpose**: Establish initial command scaffolding and directories.
|
||||
|
||||
- [X] T001 Create insert package scaffolding `internal/insert/doc.go`
|
||||
- [X] T002 Add placeholder Cobra command entry point `cmd/insert.go`
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Foundational (Blocking Prerequisites)
|
||||
|
||||
**Purpose**: Core parsing, summary structures, and helpers needed by all stories.
|
||||
|
||||
- [X] T003 Define `InsertRequest` builder and execution mode helpers in `internal/insert/request.go`
|
||||
- [X] T004 Implement `InsertSummary`, preview entries, and conflict types in `internal/insert/summary.go`
|
||||
- [X] T005 Build Unicode-aware position parsing and normalization utilities in `internal/insert/positions.go`
|
||||
|
||||
**Checkpoint**: Foundation ready — user story implementation can now begin.
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: User Story 1 – Insert Text at Target Position (Priority: P1) 🎯 MVP
|
||||
|
||||
**Goal**: Provide preview + apply flow that inserts text at specified positions with Unicode handling.
|
||||
|
||||
**Independent Test**: `renamer insert 3 _tag --dry-run` confirms preview insertion per code point ordering; `--yes` applies and ledger logs metadata.
|
||||
|
||||
### Tests
|
||||
|
||||
- [X] T006 [P] [US1] Add contract preview/apply coverage in `tests/contract/insert_command_test.go`
|
||||
- [X] T007 [P] [US1] Add integration flow test for positional insert in `tests/integration/insert_flow_test.go`
|
||||
|
||||
### Implementation
|
||||
|
||||
- [X] T008 [US1] Implement planning engine to compute proposed names in `internal/insert/engine.go`
|
||||
- [X] T009 [US1] Render preview output with highlighted segments in `internal/insert/preview.go`
|
||||
- [X] T010 [US1] Apply filesystem changes with ledger logging in `internal/insert/apply.go`
|
||||
- [X] T011 [US1] Wire Cobra command to parse args, perform preview/apply in `cmd/insert.go`
|
||||
|
||||
**Checkpoint**: User Story 1 functionality testable end-to-end.
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: User Story 2 – Automation-Friendly Batch Inserts (Priority: P2)
|
||||
|
||||
**Goal**: Ensure ledger metadata, undo, and exit codes support automation.
|
||||
|
||||
**Independent Test**: `renamer insert $ _ARCHIVE --yes --path ./fixtures` exits `0` with ledger metadata; `renamer undo` restores filenames.
|
||||
|
||||
### Tests
|
||||
|
||||
- [ ] T012 [P] [US2] Extend contract tests for ledger metadata & exit codes in `tests/contract/insert_ledger_test.go`
|
||||
- [ ] T013 [P] [US2] Add automation/undo integration scenario in `tests/integration/insert_undo_test.go`
|
||||
|
||||
### Implementation
|
||||
|
||||
- [ ] T014 [US2] Persist position token and inserted text in ledger metadata via `internal/insert/apply.go`
|
||||
- [ ] T015 [US2] Enhance undo CLI feedback for insert batches in `cmd/undo.go`
|
||||
- [ ] T016 [US2] Ensure zero-match runs exit `0` with notice in `cmd/insert.go`
|
||||
|
||||
**Checkpoint**: User Stories 1 & 2 independently verifiable.
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: User Story 3 – Validate Positions and Multilingual Inputs (Priority: P3)
|
||||
|
||||
**Goal**: Robust validation, conflict detection, and messaging for out-of-range or conflicting inserts.
|
||||
|
||||
**Independent Test**: Invalid positions produce descriptive errors; duplicate targets block apply; Chinese filenames preview correctly.
|
||||
|
||||
### Tests
|
||||
|
||||
- [X] T017 [P] [US3] Add validation/conflict contract coverage in `tests/contract/insert_validation_test.go`
|
||||
- [X] T018 [P] [US3] Add conflict-blocking integration scenario in `tests/integration/insert_validation_test.go`
|
||||
|
||||
### Implementation
|
||||
|
||||
- [X] T019 [US3] Implement parsing + error messaging for position tokens in `internal/insert/parser.go`
|
||||
- [X] T020 [US3] Detect conflicting targets and report warnings in `internal/insert/conflicts.go`
|
||||
- [X] T021 [US3] Surface validation failures and conflict gating in `cmd/insert.go`
|
||||
|
||||
**Checkpoint**: All user stories function with robust validation.
|
||||
|
||||
---
|
||||
|
||||
## Phase 6: Polish & Cross-Cutting Concerns
|
||||
|
||||
**Purpose**: Documentation, tooling, and quality improvements.
|
||||
|
||||
- [X] T022 Update CLI flags documentation for insert command in `docs/cli-flags.md`
|
||||
- [X] T023 Add insert smoke test script `scripts/smoke-test-insert.sh`
|
||||
- [X] T024 Run gofmt and `go test ./...` from repo root `./`
|
||||
|
||||
---
|
||||
|
||||
## Dependencies & Execution Order
|
||||
|
||||
### Phase Dependencies
|
||||
|
||||
1. Phase 1 (Setup) → groundwork for new command/package.
|
||||
2. Phase 2 (Foundational) → required before user story work.
|
||||
3. Phase 3 (US1) → delivers MVP after foundational tasks.
|
||||
4. Phase 4 (US2) → builds on US1 for automation support.
|
||||
5. Phase 5 (US3) → extends validation/conflict handling.
|
||||
6. Phase 6 (Polish) → final documentation and quality checks.
|
||||
|
||||
### User Story Dependencies
|
||||
|
||||
- US1 depends on foundational tasks only.
|
||||
- US2 depends on US1 implementation (ledger/apply logic).
|
||||
- US3 depends on US1 preview/apply and US2 ledger updates.
|
||||
|
||||
### Task Dependencies (selected)
|
||||
|
||||
- T008 requires T003–T005.
|
||||
- T009, T010 depend on T008.
|
||||
- T011 depends on T008–T010.
|
||||
- T014 depends on T010.
|
||||
- T015 depends on T014.
|
||||
- T019 depends on T003, T005.
|
||||
- T020 depends on T008, T009.
|
||||
- T021 depends on T019–T020.
|
||||
|
||||
---
|
||||
|
||||
## Parallel Execution Examples
|
||||
|
||||
- Within US1, tasks T006 and T007 can run in parallel once T011 is in progress.
|
||||
- Within US2, tests T012/T013 may execute while T014–T016 are implemented.
|
||||
- Within US3, contract vs integration tests (T017/T018) can proceed concurrently after T021 adjustments.
|
||||
|
||||
---
|
||||
|
||||
## Implementation Strategy
|
||||
|
||||
### MVP (US1)
|
||||
1. Complete Phases 1–2 foundation.
|
||||
2. Deliver Phase 3 (US1) to enable core insert functionality.
|
||||
3. Validate via contract/integration tests (T006/T007) and manual dry-run/apply checks.
|
||||
|
||||
### Incremental Delivery
|
||||
- Phase 4 adds automation/undo guarantees after MVP.
|
||||
- Phase 5 hardens validation and conflict management.
|
||||
- Phase 6 completes documentation, smoke coverage, and regression checks.
|
||||
|
||||
### Parallel Approach
|
||||
- One developer handles foundational + US1 engine.
|
||||
- Another focuses on test coverage and CLI wiring after foundations.
|
||||
- Additional developer can own US2 automation tasks while US1 finalizes, then US3 validation enhancements.
|
||||
Reference in New Issue
Block a user