diff --git a/Consolidate-Tasks.Tests.ps1 b/Consolidate-Tasks.Tests.ps1 index ad5428e..c6808a3 100644 --- a/Consolidate-Tasks.Tests.ps1 +++ b/Consolidate-Tasks.Tests.ps1 @@ -39,6 +39,64 @@ Describe "Get-FeatureSlug" { } } +Describe "Get-SourceDate" { + It "extracts an embedded YYYY-MM-DD filename prefix" { + $p = Join-Path $TestDrive "2026-05-12-alpha-plan.md" + Set-Content -LiteralPath $p -Value "x" + Get-SourceDate -Path $p | Should -Be "2026-05-12" + } + It "falls back to the file's last-write date when no prefix is present" { + $p = Join-Path $TestDrive "no-date-plan.md" + Set-Content -LiteralPath $p -Value "x" + $expected = (Get-Item -LiteralPath $p).LastWriteTimeUtc.ToString("yyyy-MM-dd") + Get-SourceDate -Path $p | Should -Be $expected + } + It "prefers the git last-commit date over the filesystem mtime for a tracked file" { + $repo = Join-Path $TestDrive "gitdate-repo" + New-Item -ItemType Directory -Path $repo -Force | Out-Null + Push-Location $repo + try { + & git init --quiet + & git config user.email "t@e.com"; & git config user.name "T" + & git config commit.gpgsign false + Set-Content -LiteralPath (Join-Path $repo "notes.md") -Value "hi" + & git add notes.md + $env:GIT_COMMITTER_DATE = "2024-03-04T10:00:00" + try { & git commit --quiet --date "2024-03-04T10:00:00" -m "add notes" } + finally { Remove-Item Env:\GIT_COMMITTER_DATE } + # Reset the filesystem mtime to a different day to prove git wins. + (Get-Item -LiteralPath (Join-Path $repo "notes.md")).LastWriteTimeUtc = [datetime]::Parse("2025-09-09T00:00:00Z") + } + finally { Pop-Location } + Get-SourceDate -Path (Join-Path $repo "notes.md") | Should -Be "2024-03-04" + } +} + +Describe "Get-GitDate" { + It "returns the most-recent commit date for a tracked file" { + $repo = Join-Path $TestDrive "gd-repo" + New-Item -ItemType Directory -Path $repo -Force | Out-Null + Push-Location $repo + try { + & git init --quiet + & git config user.email "t@e.com"; & git config user.name "T" + & git config commit.gpgsign false + Set-Content -LiteralPath (Join-Path $repo "f.md") -Value "x" + & git add f.md + $env:GIT_COMMITTER_DATE = "2023-07-08T12:00:00" + try { & git commit --quiet --date "2023-07-08T12:00:00" -m "c" } + finally { Remove-Item Env:\GIT_COMMITTER_DATE } + } + finally { Pop-Location } + Get-GitDate -Path (Join-Path $repo "f.md") | Should -Be "2023-07-08" + } + It "returns empty for an untracked file" { + $p = Join-Path $TestDrive "untracked.md" + Set-Content -LiteralPath $p -Value "x" + Get-GitDate -Path $p | Should -Be "" + } +} + Describe "Get-ShortHash" { It "returns 8 hex chars" { $h = Get-ShortHash -Text "hello" @@ -153,7 +211,7 @@ Describe "Invoke-ConsolidateTasks -- in-repo legacy moves" { It "with -Confirm:false, moves files via git mv (history preserved)" { $records = Invoke-ConsolidateTasks -RepoRoot $script:repo -IncludeDocsDesigns -IncludeDocsPrd -IncludeRootDocs -LinkIssues -Confirm:$false $records.Count | Should -BeGreaterOrEqual 3 - Test-Path (Join-Path $script:repo "tasks/alpha-plan.md") | Should -BeTrue + Test-Path (Join-Path $script:repo "tasks/2026-05-12-alpha-plan.md") | Should -BeTrue Test-Path (Join-Path $script:repo "tasks/beta-prd.md") | Should -BeTrue Test-Path (Join-Path $script:repo "tasks/legacy-prd.md") | Should -BeTrue Test-Path (Join-Path $script:repo "docs/designs/2026-05-12-alpha-plan.md") | Should -BeFalse @@ -169,11 +227,33 @@ Describe "Invoke-ConsolidateTasks -- in-repo legacy moves" { $manifest = Get-Content -LiteralPath $manifestPath -Raw $manifest | Should -Match "# tasks/ Migration Manifest" $manifest | Should -Match "alpha-plan.md" + $manifest | Should -Match "Source Date" + $manifest | Should -Match "2026-05-12" + } + + It "preserves the YYYY-MM-DD date prefix on the destination name" { + $records = Invoke-ConsolidateTasks -RepoRoot $script:repo -IncludeDocsDesigns -Confirm:$false + $alpha = $records | Where-Object { $_.Destination -match "alpha-plan" } + $alpha.Destination | Should -Be "tasks/2026-05-12-alpha-plan.md" + } + + It "with -InsertDatePrefix, synthesizes a date prefix for an undated source from its git commit date" { + $records = Invoke-ConsolidateTasks -RepoRoot $script:repo -IncludeDocsPrd -IncludeRootDocs -InsertDatePrefix -Confirm:$false + $beta = $records | Where-Object { $_.Destination -match "beta-prd" } + $beta.Destination | Should -Match "^tasks/\d{4}-\d{2}-\d{2}-beta-prd\.md$" + $legacy = $records | Where-Object { $_.Destination -match "legacy-prd" } + $legacy.Destination | Should -Match "^tasks/\d{4}-\d{2}-\d{2}-legacy-prd\.md$" + } + + It "with -InsertDatePrefix, an embedded date in the source name still wins" { + $records = Invoke-ConsolidateTasks -RepoRoot $script:repo -IncludeDocsDesigns -InsertDatePrefix -Confirm:$false + $alpha = $records | Where-Object { $_.Destination -match "alpha-plan" } + $alpha.Destination | Should -Be "tasks/2026-05-12-alpha-plan.md" } It "normalizes docs/designs date prefixes and docs/prd suffix conventions" { $records = Invoke-ConsolidateTasks -RepoRoot $script:repo -IncludeDocsDesigns -IncludeDocsPrd -IncludeRootDocs -LinkIssues -Confirm:$false - $records.Destination -join "`n" | Should -Match "tasks/alpha-plan.md" + $records.Destination -join "`n" | Should -Match "tasks/2026-05-12-alpha-plan.md" $records.Destination -join "`n" | Should -Match "tasks/beta-prd.md" $records.Destination -join "`n" | Should -Match "tasks/legacy-prd.md" } @@ -187,7 +267,7 @@ Describe "Invoke-ConsolidateTasks -- in-repo legacy moves" { It "is idempotent: a second run with identical source content reports skip" { Invoke-ConsolidateTasks -RepoRoot $script:repo -IncludeDocsDesigns -Confirm:$false | Out-Null New-Item -ItemType Directory -Path (Join-Path $script:repo "docs/designs") -Force | Out-Null - Copy-Item -LiteralPath (Join-Path $script:repo "tasks/alpha-plan.md") -Destination (Join-Path $script:repo "docs/designs/alpha-plan.md") + Copy-Item -LiteralPath (Join-Path $script:repo "tasks/2026-05-12-alpha-plan.md") -Destination (Join-Path $script:repo "docs/designs/2026-05-12-alpha-plan.md") $records = Invoke-ConsolidateTasks -RepoRoot $script:repo -IncludeDocsDesigns -Confirm:$false $skips = $records | Where-Object { $_.Action -eq "skip" } @($skips).Count | Should -BeGreaterOrEqual 1 @@ -196,9 +276,9 @@ Describe "Invoke-ConsolidateTasks -- in-repo legacy moves" { It "appends a sha1 suffix when destination exists with different content" { Invoke-ConsolidateTasks -RepoRoot $script:repo -IncludeDocsDesigns -Confirm:$false | Out-Null New-Item -ItemType Directory -Path (Join-Path $script:repo "docs/designs") -Force | Out-Null - Set-Content -LiteralPath (Join-Path $script:repo "docs/designs/alpha-plan.md") -Value "DIFFERENT-CONTENT" + Set-Content -LiteralPath (Join-Path $script:repo "docs/designs/2026-05-12-alpha-plan.md") -Value "DIFFERENT-CONTENT" $records = Invoke-ConsolidateTasks -RepoRoot $script:repo -IncludeDocsDesigns -Confirm:$false - ($records | Where-Object { $_.Destination -match "alpha-plan-[0-9a-f]{8}\.md" }).Count | Should -BeGreaterOrEqual 1 + ($records | Where-Object { $_.Destination -match "2026-05-12-alpha-plan-[0-9a-f]{8}\.md" }).Count | Should -BeGreaterOrEqual 1 } } @@ -231,6 +311,12 @@ Describe "Invoke-ConsolidateTasks -- copilot session sources" { Test-Path (Join-Path $script:repo "tasks/session-abcdef12-plan.md") | Should -BeTrue } + It "with -InsertDatePrefix, date-prefixes an undated session plan from its mtime" { + $records = Invoke-ConsolidateTasks -RepoRoot $script:repo -IncludeCopilotSessions -CopilotSessionRoot $script:sessionRoot -InsertDatePrefix -Confirm:$false + @($records).Count | Should -Be 1 + $records[0].Destination | Should -Match "[/\\]\d{4}-\d{2}-\d{2}-session-abcdef12-plan\.md$" + } + It "leaves the original session plan in place (copy not move)" { Invoke-ConsolidateTasks -RepoRoot $script:repo -IncludeCopilotSessions -CopilotSessionRoot $script:sessionRoot -Confirm:$false | Out-Null Test-Path (Join-Path $script:matchDir "plan.md") | Should -BeTrue @@ -258,16 +344,27 @@ Describe "Invoke-ConsolidateTasks -- copilot session sources" { Describe "Write-MigrationManifest" { It "writes a well-formed markdown table" { $records = @( - (New-MigrationRecord -Source "docs/designs/foo.md" -Destination "tasks/foo-plan.md" -Action "move" -SourceType "docs/designs" -LinkedIssues @(1, 2)), - (New-MigrationRecord -Source "~/.copilot/x/plan.md" -Destination "tasks/session-x-plan.md" -Action "copy" -SourceType "copilot-session") + (New-MigrationRecord -Source "docs/designs/foo.md" -Destination "tasks/foo-plan.md" -Action "move" -SourceType "docs/designs" -SourceDate "2024-01-01" -LinkedIssues @(1, 2)), + (New-MigrationRecord -Source "~/.copilot/x/plan.md" -Destination "tasks/session-x-plan.md" -Action "copy" -SourceType "copilot-session" -SourceDate "2024-02-02") ) $path = Join-Path $TestDrive "MIGRATION.md" Write-MigrationManifest -Path $path -Records $records $text = Get-Content -LiteralPath $path -Raw $text | Should -Match "# tasks/ Migration Manifest" $text | Should -Match "Timestamp \(UTC\)" + $text | Should -Match "Source Date" $text | Should -Match "#1 #2" $text | Should -Match "foo-plan.md" $text | Should -Match "session-x-plan.md" } + It "orders rows chronologically by source date" { + $records = @( + (New-MigrationRecord -Source "b.md" -Destination "tasks/b-plan.md" -Action "move" -SourceType "docs/designs" -SourceDate "2025-12-31"), + (New-MigrationRecord -Source "a.md" -Destination "tasks/a-plan.md" -Action "move" -SourceType "docs/designs" -SourceDate "2024-01-01") + ) + $path = Join-Path $TestDrive "MIGRATION-order.md" + Write-MigrationManifest -Path $path -Records $records + $text = Get-Content -LiteralPath $path -Raw + $text.IndexOf("a-plan.md") | Should -BeLessThan $text.IndexOf("b-plan.md") + } } \ No newline at end of file diff --git a/Consolidate-Tasks.ps1 b/Consolidate-Tasks.ps1 index 21775da..70f1fe9 100644 --- a/Consolidate-Tasks.ps1 +++ b/Consolidate-Tasks.ps1 @@ -12,15 +12,23 @@ Source policies (configurable via switches): In-repo legacy sources -- MOVED via "git mv" to preserve history: - - docs/designs/*.md -> tasks/-plan.md - - docs/prd/*.md -> tasks/-prd.md + - docs/designs/*.md -> tasks/--plan.md + - docs/prd/*.md -> tasks/--prd.md - root PRD.md / plan.md / - IMPLEMENTATION_PLAN.md -> tasks/legacy-.md + IMPLEMENTATION_PLAN.md -> tasks/-legacy-.md + + Every imported destination is date-prefixed so the tasks/ listing sorts + chronologically. The date is resolved (in order): a leading YYYY-MM-DD- + prefix already in the source name; else the date of the most recent git + commit that modified the file; else the file's last-write time. Pass + -InsertDatePrefix:$false to suppress synthesized prefixes (an embedded + prefix in the source name is always preserved). The resolved date is also + recorded in the Source Date column of tasks/MIGRATION.md. Out-of-repo session sources -- COPIED (originals stay in place): - ~/.copilot/session-state//plan.md - -> tasks/session--plan.md - - ~/.claude/... (opt-in) -> tasks/claude--plan.md + -> tasks/-session--plan.md + - ~/.claude/... (opt-in) -> tasks/-claude--plan.md Collision policy: if the destination exists with DIFFERENT content, a - suffix is appended. If it already exists with @@ -39,6 +47,7 @@ param( [switch]$IncludeCopilotSessions = $true, [switch]$IncludeClaudeSessions, [switch]$LinkIssues = $true, + [switch]$InsertDatePrefix = $true, [string]$RepoRoot, [string]$CopilotSessionRoot, [string]$ClaudeSessionRoot @@ -57,6 +66,62 @@ function Get-FeatureSlug { return $stem } +function Get-DatePrefix { + <# + .SYNOPSIS + Returns the leading YYYY-MM-DD date embedded in a filename, or "" when + none is present. The date prefix is preserved on the destination name so + the tasks/ listing sorts chronologically. + #> + [CmdletBinding()] + param([Parameter(Mandatory)][string]$FileName) + if ($FileName -match "^(\d{4}-\d{2}-\d{2})-") { return $Matches[1] } + return "" +} + +function Get-GitDate { + <# + .SYNOPSIS + Returns the date (yyyy-MM-dd) of the most recent commit that modified a + file, or "" when the file is untracked, outside a repo, or git is + unavailable. The git committer date survives copies and is more reliable + provenance than the filesystem mtime, which is reset on clone/checkout. + #> + [CmdletBinding()] + param([Parameter(Mandatory)][string]$Path) + $dir = Split-Path -Parent $Path + if (-not $dir) { $dir = "." } + try { + $out = & git -C $dir log -1 --format=%cs -- $Path 2>$null + if ($LASTEXITCODE -eq 0 -and $out) { return ($out | Select-Object -First 1).Trim() } + } + catch { } + return "" +} + +function Get-SourceDate { + <# + .SYNOPSIS + Returns the provenance date (yyyy-MM-dd) of a source file. Resolution + order: (1) an embedded YYYY-MM-DD filename prefix, (2) the date of the + most recent git commit that modified the file, (3) the file's last-write + time (UTC). Returns "" only when none of these is available. + #> + [CmdletBinding()] + param([Parameter(Mandatory)][string]$Path) + $prefix = Get-DatePrefix -FileName (Split-Path -Leaf $Path) + if ($prefix) { return $prefix } + $gitDate = Get-GitDate -Path $Path + if ($gitDate) { return $gitDate } + try { + $item = Get-Item -LiteralPath $Path -ErrorAction Stop + return $item.LastWriteTimeUtc.ToString("yyyy-MM-dd") + } + catch { + return "" + } +} + function Get-ShortHash { [CmdletBinding()] param([Parameter(Mandatory)][AllowEmptyString()][string]$Text) @@ -147,6 +212,7 @@ function New-MigrationRecord { [Parameter(Mandatory)][string]$Destination, [Parameter(Mandatory)][ValidateSet("move", "copy", "skip")] [string]$Action, [Parameter(Mandatory)][string]$SourceType, + [string]$SourceDate = "", [int[]]$LinkedIssues = @(), [string]$Note = "" ) @@ -155,6 +221,7 @@ function New-MigrationRecord { Destination = $Destination Action = $Action SourceType = $SourceType + SourceDate = $SourceDate TimestampUtc = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ") LinkedIssues = $LinkedIssues Note = $Note @@ -172,15 +239,18 @@ function Write-MigrationManifest { $lines.Add("") | Out-Null $lines.Add("Generated by ``Consolidate-Tasks.ps1``. Each row records one import.") | Out-Null $lines.Add("") | Out-Null - $lines.Add("| Timestamp (UTC) | Action | Source Type | Source | Destination | Linked Issues | Note |") | Out-Null - $lines.Add("|---|---|---|---|---|---|---|") | Out-Null - foreach ($r in $Records) { + $lines.Add("| Timestamp (UTC) | Source Date | Action | Source Type | Source | Destination | Linked Issues | Note |") | Out-Null + $lines.Add("|---|---|---|---|---|---|---|---|") | Out-Null + $ordered = $Records | Sort-Object -Stable -Property ` + @{ Expression = { if ($_.SourceDate) { $_.SourceDate } else { "9999-99-99" } } }, ` + @{ Expression = { $_.Source } } + foreach ($r in $ordered) { $issues = "" if ($r.LinkedIssues -and $r.LinkedIssues.Count -gt 0) { $issues = ($r.LinkedIssues | ForEach-Object { "#$_" }) -join " " } - $lines.Add(("| {0} | {1} | {2} | ``{3}`` | ``{4}`` | {5} | {6} |" -f ` - $r.TimestampUtc, $r.Action, $r.SourceType, $r.Source, $r.Destination, $issues, $r.Note)) | Out-Null + $lines.Add(("| {0} | {1} | {2} | {3} | ``{4}`` | ``{5}`` | {6} | {7} |" -f ` + $r.TimestampUtc, $r.SourceDate, $r.Action, $r.SourceType, $r.Source, $r.Destination, $issues, $r.Note)) | Out-Null } $utf8NoBom = New-Object System.Text.UTF8Encoding $false $dir = Split-Path -Parent $Path @@ -200,18 +270,22 @@ function Import-InRepoFile { [Parameter(Mandatory)][string]$SourceType, [string]$ForcedBaseName, [switch]$LinkIssues, + [switch]$InsertDatePrefix, $ShouldProcessHandler ) $content = Get-Content -LiteralPath $Source -Raw -ErrorAction Stop + $sourceDate = Get-SourceDate -Path $Source $slug = if ($ForcedBaseName) { $ForcedBaseName } else { Get-FeatureSlug -FileName (Split-Path -Leaf $Source) } - $base = "$slug$KindSuffix" + $embedded = Get-DatePrefix -FileName (Split-Path -Leaf $Source) + $datePrefix = if ($embedded) { $embedded } elseif ($InsertDatePrefix -and $sourceDate) { $sourceDate } else { "" } + $base = if ($datePrefix) { "$datePrefix-$slug$KindSuffix" } else { "$slug$KindSuffix" } $dest = Resolve-Destination -DestinationDir $TasksDir -BaseName $base -Extension ".md" -SourceContent $content $relSource = [System.IO.Path]::GetRelativePath($RepoRoot, $Source) -replace "\\", "/" if ($null -eq $dest) { $existingDest = Join-Path $TasksDir "$base.md" $relDest = [System.IO.Path]::GetRelativePath($RepoRoot, $existingDest) -replace "\\", "/" return New-MigrationRecord -Source $relSource -Destination $relDest ` - -Action "skip" -SourceType $SourceType ` + -Action "skip" -SourceType $SourceType -SourceDate $sourceDate ` -Note "destination already exists with identical content" } $relDest = [System.IO.Path]::GetRelativePath($RepoRoot, $dest) -replace "\\", "/" @@ -219,7 +293,7 @@ function Import-InRepoFile { if ($LinkIssues) { $issues = Find-LinkedIssues -Text $content } if ($ShouldProcessHandler -and -not $ShouldProcessHandler.ShouldProcess($relSource, "git mv -> $relDest")) { return New-MigrationRecord -Source $relSource -Destination $relDest -Action "move" ` - -SourceType $SourceType -LinkedIssues $issues -Note "planned (WhatIf)" + -SourceType $SourceType -SourceDate $sourceDate -LinkedIssues $issues -Note "planned (WhatIf)" } Push-Location $RepoRoot try { @@ -240,7 +314,7 @@ function Import-InRepoFile { } finally { Pop-Location } return New-MigrationRecord -Source $relSource -Destination $relDest -Action "move" ` - -SourceType $SourceType -LinkedIssues $issues + -SourceType $SourceType -SourceDate $sourceDate -LinkedIssues $issues } function Import-SessionFile { @@ -252,22 +326,25 @@ function Import-SessionFile { [Parameter(Mandatory)][string]$KindSuffix, [Parameter(Mandatory)][string]$SourceType, [switch]$LinkIssues, + [switch]$InsertDatePrefix, $ShouldProcessHandler ) $content = Get-Content -LiteralPath $Source -Raw -ErrorAction Stop - $base = "$BaseName$KindSuffix" + $sourceDate = Get-SourceDate -Path $Source + $datePrefix = if ($InsertDatePrefix -and $sourceDate) { $sourceDate } else { "" } + $base = if ($datePrefix) { "$datePrefix-$BaseName$KindSuffix" } else { "$BaseName$KindSuffix" } $dest = Resolve-Destination -DestinationDir $TasksDir -BaseName $base -Extension ".md" -SourceContent $content if ($null -eq $dest) { $existingDest = Join-Path $TasksDir "$base.md" return New-MigrationRecord -Source $Source -Destination $existingDest ` - -Action "skip" -SourceType $SourceType ` + -Action "skip" -SourceType $SourceType -SourceDate $sourceDate ` -Note "destination already exists with identical content" } $issues = @() if ($LinkIssues) { $issues = Find-LinkedIssues -Text $content } if ($ShouldProcessHandler -and -not $ShouldProcessHandler.ShouldProcess($Source, "Copy -> $dest")) { return New-MigrationRecord -Source $Source -Destination $dest -Action "copy" ` - -SourceType $SourceType -LinkedIssues $issues -Note "planned (WhatIf)" + -SourceType $SourceType -SourceDate $sourceDate -LinkedIssues $issues -Note "planned (WhatIf)" } $destDir = Split-Path -Parent $dest if ($destDir -and -not (Test-Path -LiteralPath $destDir)) { @@ -275,7 +352,7 @@ function Import-SessionFile { } Copy-Item -LiteralPath $Source -Destination $dest -Force return New-MigrationRecord -Source $Source -Destination $dest -Action "copy" ` - -SourceType $SourceType -LinkedIssues $issues + -SourceType $SourceType -SourceDate $sourceDate -LinkedIssues $issues } function Invoke-ConsolidateTasks { @@ -288,6 +365,7 @@ function Invoke-ConsolidateTasks { [switch]$IncludeCopilotSessions, [switch]$IncludeClaudeSessions, [switch]$LinkIssues, + [switch]$InsertDatePrefix, [string]$CopilotSessionRoot, [string]$ClaudeSessionRoot, [string]$OriginUrl @@ -314,7 +392,7 @@ function Invoke-ConsolidateTasks { $r = Import-InRepoFile -Source $_.FullName ` -RepoRoot $rootFull -TasksDir $tasksDir ` -KindSuffix "-plan" -SourceType "docs/designs" ` - -LinkIssues:$LinkIssues -ShouldProcessHandler $PSCmdlet + -LinkIssues:$LinkIssues -InsertDatePrefix:$InsertDatePrefix -ShouldProcessHandler $PSCmdlet if ($r) { $records.Add($r) | Out-Null } } } @@ -327,7 +405,7 @@ function Invoke-ConsolidateTasks { $r = Import-InRepoFile -Source $_.FullName ` -RepoRoot $rootFull -TasksDir $tasksDir ` -KindSuffix "-prd" -SourceType "docs/prd" ` - -LinkIssues:$LinkIssues -ShouldProcessHandler $PSCmdlet + -LinkIssues:$LinkIssues -InsertDatePrefix:$InsertDatePrefix -ShouldProcessHandler $PSCmdlet if ($r) { $records.Add($r) | Out-Null } } } @@ -346,7 +424,7 @@ function Invoke-ConsolidateTasks { -RepoRoot $rootFull -TasksDir $tasksDir ` -KindSuffix $rootMap[$name] -SourceType "root-doc" ` -ForcedBaseName "legacy" ` - -LinkIssues:$LinkIssues -ShouldProcessHandler $PSCmdlet + -LinkIssues:$LinkIssues -InsertDatePrefix:$InsertDatePrefix -ShouldProcessHandler $PSCmdlet if ($r) { $records.Add($r) | Out-Null } } } @@ -366,7 +444,7 @@ function Invoke-ConsolidateTasks { $r = Import-SessionFile -Source $plan ` -TasksDir $tasksDir -BaseName $base -KindSuffix "-plan" ` -SourceType "copilot-session" ` - -LinkIssues:$LinkIssues -ShouldProcessHandler $PSCmdlet + -LinkIssues:$LinkIssues -InsertDatePrefix:$InsertDatePrefix -ShouldProcessHandler $PSCmdlet if ($r) { $records.Add($r) | Out-Null } } } @@ -381,7 +459,7 @@ function Invoke-ConsolidateTasks { $r = Import-SessionFile -Source $_.FullName ` -TasksDir $tasksDir -BaseName $base -KindSuffix "-plan" ` -SourceType "claude-session" ` - -LinkIssues:$LinkIssues -ShouldProcessHandler $PSCmdlet + -LinkIssues:$LinkIssues -InsertDatePrefix:$InsertDatePrefix -ShouldProcessHandler $PSCmdlet if ($r) { $records.Add($r) | Out-Null } } } @@ -422,6 +500,7 @@ if ($MyInvocation.InvocationName -ne ".") { -IncludeCopilotSessions:$IncludeCopilotSessions ` -IncludeClaudeSessions:$IncludeClaudeSessions ` -LinkIssues:$LinkIssues ` + -InsertDatePrefix:$InsertDatePrefix ` -CopilotSessionRoot $CopilotSessionRoot ` -ClaudeSessionRoot $ClaudeSessionRoot ` -OriginUrl $originUrl ` diff --git a/tasks/README.md b/tasks/README.md index cf1b697..4701832 100644 --- a/tasks/README.md +++ b/tasks/README.md @@ -49,12 +49,25 @@ so its contents are **never** touched by upstream sync. Edit freely; the next The repo-root script `Consolidate-Tasks.ps1` imports historical spec artifacts into this directory: -- `docs/designs/*.md` -> moved (`git mv`) and renamed to `-plan.md` -- `docs/prd/*.md` -> moved (`git mv`) and renamed to `-prd.md` +- `docs/designs/*.md` -> moved (`git mv`) and renamed to `--plan.md` +- `docs/prd/*.md` -> moved (`git mv`) and renamed to `--prd.md` - Root `PRD.md`, `plan.md`, `IMPLEMENTATION_PLAN.md` -> moved - `~/.copilot/session-state/*/plan.md` -> copied (repo-scoped via the session-store DB), default ON - `~/.claude/...` session notes -> copied, opt-in (`-IncludeClaudeSessions`) +Every imported destination is **date-prefixed** so the `tasks/` listing sorts +chronologically. The date is resolved in order: a leading `YYYY-MM-DD-` prefix +already present in the source name; else the date of the most recent git commit +that modified the file; else the file's last-write time. Pass +`-InsertDatePrefix:$false` to suppress synthesized prefixes (an embedded prefix +in the source name is always preserved). The resolved date is also recorded in +the `Source Date` column of `tasks/MIGRATION.md`. + +> This date convention applies only to **migrated legacy artifacts**, whose +> original ordering would otherwise be lost. Newly created `@prd` / `@plan` +> files keep the bare `-prd.md` / `-plan.md` slug so the +> agents can resolve them by exact path. + It uses the standard PowerShell `SupportsShouldProcess` pattern: ```powershell