dsh-plugin-guide 0.3.29 → 0.3.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,63 +1,63 @@
1
- # dsh-plugin-guide skill installer.
2
- # Purpose: copy the KB (excluding generated/local-only parts) into an agent skill directory and verify byte parity.
3
- # Usage: pwsh -File scripts/install-skill.ps1 -Target <skill-dir> [-Source <kb-root>]
4
- # Scope: SKILL.md, guide/, references/ (incl. official-docs), scripts/, README*.md, LICENSE, NOTICE.md, CONTRIBUTING.md, SECURITY.md.
5
- # Excluded: downloads/ (generated by scripts), .github/ (CI), .gitignore.
6
- # Target must be the skill directory itself (e.g. ~/.deepseek/skills/dsh-plugin-guide or <project>/.agents/skills/dsh-plugin-guide).
7
- param(
8
- [Parameter(Mandatory=$true)][string]$Target,
9
- [string]$Source = ''
10
- )
11
- $ErrorActionPreference = 'Stop'
12
- if (-not $Source) { $Source = Split-Path $PSScriptRoot -Parent }
13
- $src = (Resolve-Path $Source).Path
14
- $top = @('SKILL.md','LICENSE','NOTICE.md','CONTRIBUTING.md','SECURITY.md') + @(Get-ChildItem $src -Filter 'README*.md' -File | ForEach-Object { $_.Name })
15
- $dirs = @('guide','references','scripts')
16
-
17
- New-Item -ItemType Directory -Force -Path $Target | Out-Null
18
-
19
- foreach ($f in $top) { Copy-Item (Join-Path $src $f) (Join-Path $Target $f) -Force }
20
- foreach ($d in $dirs) {
21
- # Copy-Item -Recurse into an existing directory would nest the source inside it; replace the owned dir instead.
22
- $td = Join-Path $Target $d
23
- if (Test-Path $td) { Remove-Item $td -Recurse -Force }
24
- Copy-Item (Join-Path $src $d) $td -Recurse -Force
25
- }
26
-
27
- # Prune target files under the owned scope that no longer exist in the source (keeps reinstalls clean).
28
- foreach ($d in $dirs) {
29
- $td = Join-Path $Target $d
30
- if (Test-Path $td) {
31
- $sd = Join-Path $src $d
32
- $sRoot = (Resolve-Path $sd).Path
33
- $tRoot = (Resolve-Path $td).Path
34
- Get-ChildItem -Recurse -File $td | ForEach-Object {
35
- $rel = $_.FullName.Substring($tRoot.Length + 1)
36
- if (-not (Test-Path (Join-Path $sRoot $rel))) { Remove-Item $_.FullName -Force }
37
- }
38
- }
39
- }
40
- Get-ChildItem $Target -File -ErrorAction SilentlyContinue | ForEach-Object {
41
- if (($_.Name -like 'README*.md' -or $top -contains $_.Name) -and -not (Test-Path (Join-Path $src $_.Name))) { Remove-Item $_.FullName -Force }
42
- }
43
-
44
- # Parity report: every scoped source file must exist in the target byte-for-byte.
45
- $checked = 0; $mismatch = 0
46
- foreach ($f in $top) {
47
- $s = Join-Path $src $f; $t = Join-Path $Target $f
48
- if (Test-Path $t) { $checked++; if ((Get-FileHash $s -Algorithm MD5).Hash -ne (Get-FileHash $t -Algorithm MD5).Hash) { $mismatch++; Write-Output "MISMATCH: $f" } }
49
- else { $mismatch++; Write-Output "MISSING-TARGET: $f" }
50
- }
51
- foreach ($d in $dirs) {
52
- $sd = Join-Path $src $d
53
- $sRoot = (Resolve-Path $sd).Path
54
- Get-ChildItem -Recurse -File $sd | ForEach-Object {
55
- $rel = $_.FullName.Substring($sRoot.Length + 1)
56
- $t = Join-Path (Join-Path $Target $d) $rel
57
- if (Test-Path $t) { $checked++; if ((Get-FileHash $_.FullName -Algorithm MD5).Hash -ne (Get-FileHash $t -Algorithm MD5).Hash) { $mismatch++; Write-Output "MISMATCH: $d\$rel" } }
58
- else { $mismatch++; Write-Output "MISSING-TARGET: $d\$rel" }
59
- }
60
- }
61
- Write-Output "DONE: $checked scoped files verified, $mismatch mismatches -> $Target"
62
- if ($mismatch -gt 0) { exit 1 }
63
- exit 0
1
+ # dsh-plugin-guide skill installer.
2
+ # Purpose: copy the KB (excluding generated/local-only parts) into an agent skill directory and verify byte parity.
3
+ # Usage: pwsh -File scripts/install-skill.ps1 -Target <skill-dir> [-Source <kb-root>]
4
+ # Scope: SKILL.md, guide/, references/ (incl. official-docs), scripts/, README*.md, LICENSE, NOTICE.md, CONTRIBUTING.md, SECURITY.md.
5
+ # Excluded: downloads/ (generated by scripts), .github/ (CI), .gitignore.
6
+ # Target must be the skill directory itself (e.g. ~/.deepseek/skills/dsh-plugin-guide or <project>/.agents/skills/dsh-plugin-guide).
7
+ param(
8
+ [Parameter(Mandatory=$true)][string]$Target,
9
+ [string]$Source = ''
10
+ )
11
+ $ErrorActionPreference = 'Stop'
12
+ if (-not $Source) { $Source = Split-Path $PSScriptRoot -Parent }
13
+ $src = (Resolve-Path $Source).Path
14
+ $top = @('SKILL.md','LICENSE','NOTICE.md','CONTRIBUTING.md','SECURITY.md') + @(Get-ChildItem $src -Filter 'README*.md' -File | ForEach-Object { $_.Name })
15
+ $dirs = @('guide','references','scripts')
16
+
17
+ New-Item -ItemType Directory -Force -Path $Target | Out-Null
18
+
19
+ foreach ($f in $top) { Copy-Item (Join-Path $src $f) (Join-Path $Target $f) -Force }
20
+ foreach ($d in $dirs) {
21
+ # Copy-Item -Recurse into an existing directory would nest the source inside it; replace the owned dir instead.
22
+ $td = Join-Path $Target $d
23
+ if (Test-Path $td) { Remove-Item $td -Recurse -Force }
24
+ Copy-Item (Join-Path $src $d) $td -Recurse -Force
25
+ }
26
+
27
+ # Prune target files under the owned scope that no longer exist in the source (keeps reinstalls clean).
28
+ foreach ($d in $dirs) {
29
+ $td = Join-Path $Target $d
30
+ if (Test-Path $td) {
31
+ $sd = Join-Path $src $d
32
+ $sRoot = (Resolve-Path $sd).Path
33
+ $tRoot = (Resolve-Path $td).Path
34
+ Get-ChildItem -Recurse -File $td | ForEach-Object {
35
+ $rel = $_.FullName.Substring($tRoot.Length + 1)
36
+ if (-not (Test-Path (Join-Path $sRoot $rel))) { Remove-Item $_.FullName -Force }
37
+ }
38
+ }
39
+ }
40
+ Get-ChildItem $Target -File -ErrorAction SilentlyContinue | ForEach-Object {
41
+ if (($_.Name -like 'README*.md' -or $top -contains $_.Name) -and -not (Test-Path (Join-Path $src $_.Name))) { Remove-Item $_.FullName -Force }
42
+ }
43
+
44
+ # Parity report: every scoped source file must exist in the target byte-for-byte.
45
+ $checked = 0; $mismatch = 0
46
+ foreach ($f in $top) {
47
+ $s = Join-Path $src $f; $t = Join-Path $Target $f
48
+ if (Test-Path $t) { $checked++; if ((Get-FileHash $s -Algorithm MD5).Hash -ne (Get-FileHash $t -Algorithm MD5).Hash) { $mismatch++; Write-Output "MISMATCH: $f" } }
49
+ else { $mismatch++; Write-Output "MISSING-TARGET: $f" }
50
+ }
51
+ foreach ($d in $dirs) {
52
+ $sd = Join-Path $src $d
53
+ $sRoot = (Resolve-Path $sd).Path
54
+ Get-ChildItem -Recurse -File $sd | ForEach-Object {
55
+ $rel = $_.FullName.Substring($sRoot.Length + 1)
56
+ $t = Join-Path (Join-Path $Target $d) $rel
57
+ if (Test-Path $t) { $checked++; if ((Get-FileHash $_.FullName -Algorithm MD5).Hash -ne (Get-FileHash $t -Algorithm MD5).Hash) { $mismatch++; Write-Output "MISMATCH: $d\$rel" } }
58
+ else { $mismatch++; Write-Output "MISSING-TARGET: $d\$rel" }
59
+ }
60
+ }
61
+ Write-Output "DONE: $checked scoped files verified, $mismatch mismatches -> $Target"
62
+ if ($mismatch -gt 0) { exit 1 }
63
+ exit 0
@@ -1,138 +1,138 @@
1
- # dsh-plugin-guide official-docs sync script.
2
- # Purpose: sync the deepseek-harness checkout's official docs into references/official-docs/ (verbatim copy).
3
- # Source is pinned to origin/master (HEAD fallback): untracked drafts and unpushed commits in the checkout never enter the KB.
4
- # Output: references/official-docs/SNAPSHOT.md (source ref/SHA, sync time, file counts - the freshness authority for READMEs).
5
- # Usage: pwsh -File scripts/sync-official-docs.ps1 [-Checkout <path>] [-SkipGit]
6
- # Verify: pwsh -File scripts/verify-kit.ps1 -Checkout <path> # drift report KB vs checkout (tracked files only)
7
- param(
8
- [string]$Checkout = 'D:\deepseek-harness',
9
- [switch]$SkipGit
10
- )
11
- $ErrorActionPreference = 'Stop'
12
- $kit = Split-Path $PSScriptRoot -Parent
13
- $dest = Join-Path $kit 'references\official-docs'
14
-
15
- if (-not (Test-Path $Checkout)) { Write-Error "checkout not found: $Checkout"; exit 1 }
16
-
17
- $gitOk = $false
18
- if (-not $SkipGit) {
19
- $gitOk = (& git -C $Checkout rev-parse --is-inside-work-tree 2>$null) -eq 'true'
20
- }
21
-
22
- $ref = 'filesystem'
23
- $sha = ''
24
- $pruned = 0
25
- $mdCount = 0
26
- $zhCount = 0
27
-
28
- # Sync scope: docs/, root AGENTS.md/BENCHMARK.md/CLAUDE.md/CONTRIBUTING.*/README.zh.md/README.i18n.yaml/THIRD_PARTY_NOTICES.md/LICENSE, packages/AGENTS.md, packages/README.md, vendor/README.md, website/docs.ts
29
- # Upstream root README.md is out of scope: official-docs/README.md is the KB-owned index (the upstream English README snapshot lives in downloads/github/harness/README.md).
30
- # Every pathspec uses the :(top) anchor so git's basename-wide matching cannot sweep in same-named symlinks (e.g. the various CLAUDE.md files).
31
- $paths = @(':(top)docs', ':(top)AGENTS.md', ':(top)BENCHMARK.md', ':(top)CONTRIBUTING.md', ':(top)CONTRIBUTING.zh.md', ':(top)CONTRIBUTING.i18n.yaml', ':(top)README.zh.md', ':(top)README.i18n.yaml', ':(top)THIRD_PARTY_NOTICES.md', ':(top)LICENSE', ':(top)packages/AGENTS.md', ':(top)packages/README.md', ':(top)vendor/README.md', ':(top)website/docs.ts')
32
-
33
- if ($gitOk) {
34
- # Prefer origin/master, fall back to HEAD - never the working tree (may contain untracked/unpushed content).
35
- if ((& git -C $Checkout rev-parse --verify -q origin/master 2>$null)) { $ref = 'origin/master' } else { $ref = 'HEAD' }
36
- $sha = (& git -C $Checkout rev-parse $ref).Trim()
37
-
38
- $tmp = Join-Path ([System.IO.Path]::GetTempPath()) ("dsh-official-docs-$sha.tar")
39
- Remove-Item $tmp -Force -ErrorAction SilentlyContinue
40
- & git -C $Checkout archive -o $tmp $ref -- $paths
41
- if ($LASTEXITCODE -ne 0) { Write-Error "git archive failed (ref=$ref)"; exit 1 }
42
-
43
- New-Item -ItemType Directory -Force -Path $dest | Out-Null
44
- # Verbatim copy: clear the destination before extracting (keep only the KB-owned index README.md,
45
- # which is unrelated to upstream), so structural residue from older snapshots (e.g. a top-level
46
- # .agents/ directory from a previous tree) cannot interfere with the extraction.
47
- Get-ChildItem -Force $dest | Where-Object { $_.Name -ne 'README.md' } | ForEach-Object {
48
- Remove-Item $_.FullName -Recurse -Force; $pruned++
49
- }
50
- & tar -xf $tmp -C $dest
51
- if ($LASTEXITCODE -ne 0) { Write-Error 'tar extraction failed'; exit 1 }
52
- Remove-Item $tmp -Force
53
-
54
- # CLAUDE.md is a symlink upstream (target AGENTS.md, mode 120000); Windows tar cannot extract
55
- # symlinks, and the 'CLAUDE.md' pathspec would basename-match same-named symlinks deeper in the
56
- # repo, so it stays out of $paths - after extraction its target text lands as a plain file
57
- # (content = the upstream blob text).
58
- $claudeTarget = (& git -C $Checkout cat-file -p "${ref}:CLAUDE.md" 2>$null | Select-Object -First 1)
59
- if (-not $claudeTarget) { $claudeTarget = 'AGENTS.md' }
60
- Set-Content -Path (Join-Path $dest 'CLAUDE.md') -Value $claudeTarget -Encoding UTF8
61
-
62
- # website/docs.ts keeps its in-repo filename in the archive; move it to the KB's flat name.
63
- if (Test-Path (Join-Path $dest 'website\docs.ts')) {
64
- Copy-Item (Join-Path $dest 'website\docs.ts') (Join-Path $dest 'website-docs.ts') -Force
65
- Remove-Item (Join-Path $dest 'website\docs.ts') -Force
66
- Remove-Item (Join-Path $dest 'website') -Force -Recurse -ErrorAction SilentlyContinue
67
- }
68
-
69
- # Keep only in-scope entries at the destination root (drops stale extraction residue and
70
- # upstream-deleted root files so the copy stays verbatim).
71
- $rootKeep = @('docs','examples','packages','vendor','AGENTS.md','BENCHMARK.md','CLAUDE.md','CONTRIBUTING.md','CONTRIBUTING.zh.md','CONTRIBUTING.i18n.yaml','LICENSE','README.zh.md','README.i18n.yaml','THIRD_PARTY_NOTICES.md','SNAPSHOT.md','README.md','website-docs.ts')
72
- Get-ChildItem -Force $dest | Where-Object { $rootKeep -notcontains $_.Name } | ForEach-Object {
73
- Remove-Item $_.FullName -Recurse -Force; $pruned++
74
- }
75
- foreach ($sub in @('examples','packages','vendor')) {
76
- $subDir = Join-Path $dest $sub
77
- if (Test-Path $subDir) {
78
- $subKeep = @()
79
- if ($sub -eq 'examples') { $subKeep = @('AGENTS.md') }
80
- if ($sub -eq 'packages') { $subKeep = @('AGENTS.md','README.md') }
81
- if ($sub -eq 'vendor') { $subKeep = @('README.md') }
82
- Get-ChildItem -Force -Directory $subDir -ErrorAction SilentlyContinue | ForEach-Object {
83
- Remove-Item $_.FullName -Recurse -Force; $pruned++
84
- }
85
- Get-ChildItem -Force -File $subDir -ErrorAction SilentlyContinue | Where-Object { $subKeep -notcontains $_.Name } | ForEach-Object {
86
- Remove-Item $_.FullName -Force; $pruned++
87
- }
88
- }
89
- }
90
-
91
- # Prune docs/ files that no longer exist in the source ref (a verbatim copy does not keep upstream-deleted content).
92
- $tracked = New-Object System.Collections.Generic.HashSet[string]
93
- foreach ($t in (& git -C $Checkout ls-tree -r --name-only $ref -- docs)) { [void]$tracked.Add($t.Replace('\','/')) }
94
- $docsDir = Join-Path $dest 'docs'
95
- if (Test-Path $docsDir) {
96
- $docsRoot = (Resolve-Path $docsDir).Path
97
- Get-ChildItem -Recurse -File $docsDir | ForEach-Object {
98
- $rel = 'docs/' + $_.FullName.Substring($docsRoot.Length + 1).Replace('\','/')
99
- if (-not $tracked.Contains($rel)) { Remove-Item $_.FullName -Force; $pruned++ }
100
- }
101
- }
102
- } else {
103
- Write-Output 'WARN: checkout is not a git repo (or -SkipGit) - copying the filesystem as-is; untracked files are not filtered and no pruning runs.'
104
- foreach ($p in $paths) {
105
- $rel = $p -replace '^:\(top\)','' # strip the pathspec anchor to get the in-repo relative path
106
- $srcFull = Join-Path $Checkout ($rel -replace '/','\')
107
- if (-not (Test-Path $srcFull)) { Write-Output "SKIP(source missing): $rel"; continue }
108
- $target = Join-Path $dest ($rel -replace '/','\')
109
- $tdir = Split-Path $target -Parent
110
- if ($tdir -and -not (Test-Path $tdir)) { New-Item -ItemType Directory -Force -Path $tdir | Out-Null }
111
- Copy-Item $srcFull $target -Recurse -Force
112
- }
113
- }
114
-
115
- $mdCount = (Get-ChildItem -Recurse -File (Join-Path $dest 'docs') -Filter *.md).Count
116
- $zhCount = (Get-ChildItem -Recurse -File (Join-Path $dest 'docs') -Filter *.zh.md).Count
117
- $utc = (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ')
118
- $L = @(
119
- '# official-docs snapshot (SNAPSHOT.md)',
120
- '',
121
- '> Generated by `scripts/sync-official-docs.ps1`. This file is the single authority for how `references/official-docs/` lines up with upstream deepseek-harness.',
122
- '> The "last verified" dates and commit SHAs in README/guide must cite this file; do not hand-edit them.',
123
- '',
124
- '| Item | Value |',
125
- '|---|---|',
126
- "| Source checkout | ``$Checkout`` |",
127
- "| Source ref | ``$ref`` |",
128
- "| Source commit | ``$sha`` |",
129
- "| Synced at (UTC) | $utc |",
130
- '| Scope | tracked files at the ref: `docs/`, root `AGENTS.md`, `BENCHMARK.md`, `CLAUDE.md` (symlink target text), `CONTRIBUTING.md`/`.zh.md`/`.i18n.yaml`, `README.zh.md`/`.i18n.yaml` (the upstream English `README.md` is not in this dir; its snapshot lives in `downloads/github/harness/README.md`), `THIRD_PARTY_NOTICES.md`, `LICENSE`, `packages/AGENTS.md`, `packages/README.md`, `vendor/README.md`, `website/docs.ts` |',
131
- "| File counts | docs/: $mdCount md files ($zhCount .zh.md pairs) |",
132
- "| Pruned this run | $pruned out-of-scope/upstream-deleted entries |",
133
- '',
134
- 'Drift check: `pwsh -File scripts/verify-kit.ps1 -Checkout <checkout>` (tracked files only).'
135
- )
136
- Set-Content -Path (Join-Path $dest 'SNAPSHOT.md') -Value ($L -join "`r`n") -Encoding UTF8
137
-
138
- Write-Output "DONE: ref=$ref sha=$sha pruned=$pruned -> $dest"
1
+ # dsh-plugin-guide official-docs sync script.
2
+ # Purpose: sync the deepseek-harness checkout's official docs into references/official-docs/ (verbatim copy).
3
+ # Source is pinned to origin/master (HEAD fallback): untracked drafts and unpushed commits in the checkout never enter the KB.
4
+ # Output: references/official-docs/SNAPSHOT.md (source ref/SHA, sync time, file counts - the freshness authority for READMEs).
5
+ # Usage: pwsh -File scripts/sync-official-docs.ps1 [-Checkout <path>] [-SkipGit]
6
+ # Verify: pwsh -File scripts/verify-kit.ps1 -Checkout <path> # drift report KB vs checkout (tracked files only)
7
+ param(
8
+ [string]$Checkout = 'D:\deepseek-harness',
9
+ [switch]$SkipGit
10
+ )
11
+ $ErrorActionPreference = 'Stop'
12
+ $kit = Split-Path $PSScriptRoot -Parent
13
+ $dest = Join-Path $kit 'references\official-docs'
14
+
15
+ if (-not (Test-Path $Checkout)) { Write-Error "checkout not found: $Checkout"; exit 1 }
16
+
17
+ $gitOk = $false
18
+ if (-not $SkipGit) {
19
+ $gitOk = (& git -C $Checkout rev-parse --is-inside-work-tree 2>$null) -eq 'true'
20
+ }
21
+
22
+ $ref = 'filesystem'
23
+ $sha = ''
24
+ $pruned = 0
25
+ $mdCount = 0
26
+ $zhCount = 0
27
+
28
+ # Sync scope: docs/, root AGENTS.md/BENCHMARK.md/CLAUDE.md/CONTRIBUTING.*/README.zh.md/README.i18n.yaml/THIRD_PARTY_NOTICES.md/LICENSE, packages/AGENTS.md, packages/README.md, vendor/README.md, website/docs.ts
29
+ # Upstream root README.md is out of scope: official-docs/README.md is the KB-owned index (the upstream English README snapshot lives in downloads/github/harness/README.md).
30
+ # Every pathspec uses the :(top) anchor so git's basename-wide matching cannot sweep in same-named symlinks (e.g. the various CLAUDE.md files).
31
+ $paths = @(':(top)docs', ':(top)AGENTS.md', ':(top)BENCHMARK.md', ':(top)CONTRIBUTING.md', ':(top)CONTRIBUTING.zh.md', ':(top)CONTRIBUTING.i18n.yaml', ':(top)README.zh.md', ':(top)README.i18n.yaml', ':(top)THIRD_PARTY_NOTICES.md', ':(top)LICENSE', ':(top)packages/AGENTS.md', ':(top)packages/README.md', ':(top)vendor/README.md', ':(top)website/docs.ts')
32
+
33
+ if ($gitOk) {
34
+ # Prefer origin/master, fall back to HEAD - never the working tree (may contain untracked/unpushed content).
35
+ if ((& git -C $Checkout rev-parse --verify -q origin/master 2>$null)) { $ref = 'origin/master' } else { $ref = 'HEAD' }
36
+ $sha = (& git -C $Checkout rev-parse $ref).Trim()
37
+
38
+ $tmp = Join-Path ([System.IO.Path]::GetTempPath()) ("dsh-official-docs-$sha.tar")
39
+ Remove-Item $tmp -Force -ErrorAction SilentlyContinue
40
+ & git -C $Checkout archive -o $tmp $ref -- $paths
41
+ if ($LASTEXITCODE -ne 0) { Write-Error "git archive failed (ref=$ref)"; exit 1 }
42
+
43
+ New-Item -ItemType Directory -Force -Path $dest | Out-Null
44
+ # Verbatim copy: clear the destination before extracting (keep only the KB-owned index README.md,
45
+ # which is unrelated to upstream), so structural residue from older snapshots (e.g. a top-level
46
+ # .agents/ directory from a previous tree) cannot interfere with the extraction.
47
+ Get-ChildItem -Force $dest | Where-Object { $_.Name -ne 'README.md' } | ForEach-Object {
48
+ Remove-Item $_.FullName -Recurse -Force; $pruned++
49
+ }
50
+ & tar -xf $tmp -C $dest
51
+ if ($LASTEXITCODE -ne 0) { Write-Error 'tar extraction failed'; exit 1 }
52
+ Remove-Item $tmp -Force
53
+
54
+ # CLAUDE.md is a symlink upstream (target AGENTS.md, mode 120000); Windows tar cannot extract
55
+ # symlinks, and the 'CLAUDE.md' pathspec would basename-match same-named symlinks deeper in the
56
+ # repo, so it stays out of $paths - after extraction its target text lands as a plain file
57
+ # (content = the upstream blob text).
58
+ $claudeTarget = (& git -C $Checkout cat-file -p "${ref}:CLAUDE.md" 2>$null | Select-Object -First 1)
59
+ if (-not $claudeTarget) { $claudeTarget = 'AGENTS.md' }
60
+ Set-Content -Path (Join-Path $dest 'CLAUDE.md') -Value $claudeTarget -Encoding UTF8
61
+
62
+ # website/docs.ts keeps its in-repo filename in the archive; move it to the KB's flat name.
63
+ if (Test-Path (Join-Path $dest 'website\docs.ts')) {
64
+ Copy-Item (Join-Path $dest 'website\docs.ts') (Join-Path $dest 'website-docs.ts') -Force
65
+ Remove-Item (Join-Path $dest 'website\docs.ts') -Force
66
+ Remove-Item (Join-Path $dest 'website') -Force -Recurse -ErrorAction SilentlyContinue
67
+ }
68
+
69
+ # Keep only in-scope entries at the destination root (drops stale extraction residue and
70
+ # upstream-deleted root files so the copy stays verbatim).
71
+ $rootKeep = @('docs','examples','packages','vendor','AGENTS.md','BENCHMARK.md','CLAUDE.md','CONTRIBUTING.md','CONTRIBUTING.zh.md','CONTRIBUTING.i18n.yaml','LICENSE','README.zh.md','README.i18n.yaml','THIRD_PARTY_NOTICES.md','SNAPSHOT.md','README.md','website-docs.ts')
72
+ Get-ChildItem -Force $dest | Where-Object { $rootKeep -notcontains $_.Name } | ForEach-Object {
73
+ Remove-Item $_.FullName -Recurse -Force; $pruned++
74
+ }
75
+ foreach ($sub in @('examples','packages','vendor')) {
76
+ $subDir = Join-Path $dest $sub
77
+ if (Test-Path $subDir) {
78
+ $subKeep = @()
79
+ if ($sub -eq 'examples') { $subKeep = @('AGENTS.md') }
80
+ if ($sub -eq 'packages') { $subKeep = @('AGENTS.md','README.md') }
81
+ if ($sub -eq 'vendor') { $subKeep = @('README.md') }
82
+ Get-ChildItem -Force -Directory $subDir -ErrorAction SilentlyContinue | ForEach-Object {
83
+ Remove-Item $_.FullName -Recurse -Force; $pruned++
84
+ }
85
+ Get-ChildItem -Force -File $subDir -ErrorAction SilentlyContinue | Where-Object { $subKeep -notcontains $_.Name } | ForEach-Object {
86
+ Remove-Item $_.FullName -Force; $pruned++
87
+ }
88
+ }
89
+ }
90
+
91
+ # Prune docs/ files that no longer exist in the source ref (a verbatim copy does not keep upstream-deleted content).
92
+ $tracked = New-Object System.Collections.Generic.HashSet[string]
93
+ foreach ($t in (& git -C $Checkout ls-tree -r --name-only $ref -- docs)) { [void]$tracked.Add($t.Replace('\','/')) }
94
+ $docsDir = Join-Path $dest 'docs'
95
+ if (Test-Path $docsDir) {
96
+ $docsRoot = (Resolve-Path $docsDir).Path
97
+ Get-ChildItem -Recurse -File $docsDir | ForEach-Object {
98
+ $rel = 'docs/' + $_.FullName.Substring($docsRoot.Length + 1).Replace('\','/')
99
+ if (-not $tracked.Contains($rel)) { Remove-Item $_.FullName -Force; $pruned++ }
100
+ }
101
+ }
102
+ } else {
103
+ Write-Output 'WARN: checkout is not a git repo (or -SkipGit) - copying the filesystem as-is; untracked files are not filtered and no pruning runs.'
104
+ foreach ($p in $paths) {
105
+ $rel = $p -replace '^:\(top\)','' # strip the pathspec anchor to get the in-repo relative path
106
+ $srcFull = Join-Path $Checkout ($rel -replace '/','\')
107
+ if (-not (Test-Path $srcFull)) { Write-Output "SKIP(source missing): $rel"; continue }
108
+ $target = Join-Path $dest ($rel -replace '/','\')
109
+ $tdir = Split-Path $target -Parent
110
+ if ($tdir -and -not (Test-Path $tdir)) { New-Item -ItemType Directory -Force -Path $tdir | Out-Null }
111
+ Copy-Item $srcFull $target -Recurse -Force
112
+ }
113
+ }
114
+
115
+ $mdCount = (Get-ChildItem -Recurse -File (Join-Path $dest 'docs') -Filter *.md).Count
116
+ $zhCount = (Get-ChildItem -Recurse -File (Join-Path $dest 'docs') -Filter *.zh.md).Count
117
+ $utc = (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ')
118
+ $L = @(
119
+ '# official-docs snapshot (SNAPSHOT.md)',
120
+ '',
121
+ '> Generated by `scripts/sync-official-docs.ps1`. This file is the single authority for how `references/official-docs/` lines up with upstream deepseek-harness.',
122
+ '> The "last verified" dates and commit SHAs in README/guide must cite this file; do not hand-edit them.',
123
+ '',
124
+ '| Item | Value |',
125
+ '|---|---|',
126
+ "| Source checkout | ``$Checkout`` |",
127
+ "| Source ref | ``$ref`` |",
128
+ "| Source commit | ``$sha`` |",
129
+ "| Synced at (UTC) | $utc |",
130
+ '| Scope | tracked files at the ref: `docs/`, root `AGENTS.md`, `BENCHMARK.md`, `CLAUDE.md` (symlink target text), `CONTRIBUTING.md`/`.zh.md`/`.i18n.yaml`, `README.zh.md`/`.i18n.yaml` (the upstream English `README.md` is not in this dir; its snapshot lives in `downloads/github/harness/README.md`), `THIRD_PARTY_NOTICES.md`, `LICENSE`, `packages/AGENTS.md`, `packages/README.md`, `vendor/README.md`, `website/docs.ts` |',
131
+ "| File counts | docs/: $mdCount md files ($zhCount .zh.md pairs) |",
132
+ "| Pruned this run | $pruned out-of-scope/upstream-deleted entries |",
133
+ '',
134
+ 'Drift check: `pwsh -File scripts/verify-kit.ps1 -Checkout <checkout>` (tracked files only).'
135
+ )
136
+ Set-Content -Path (Join-Path $dest 'SNAPSHOT.md') -Value ($L -join "`r`n") -Encoding UTF8
137
+
138
+ Write-Output "DONE: ref=$ref sha=$sha pruned=$pruned -> $dest"
@@ -58,6 +58,32 @@ try {
58
58
  const readmeCount = readdirSync(target).filter((f) => /^README(-\w{2})?\.md$/.test(f)).length
59
59
  check(result.status === 0 && hasEntry && hasPatch, `${lang} scaffold produces entry + cordis.patch.yml (exit ${result.status})`)
60
60
  check(readmeCount === 5, `${lang} scaffold produces 5 README languages (${readmeCount})`)
61
+
62
+ // Round-trip the scaffolder through the checker. This is the assertion whose
63
+ // absence let `new --lang js` fail `check` for as long as it did: the
64
+ // scaffolder and the checker disagreed about whether a build-artifact
65
+ // directory is mandatory, and nothing ever ran one against the other. The
66
+ // golden path a user follows is exactly this pair, so it is the pair the
67
+ // artifact gate must exercise.
68
+ const scaffoldCheck = runNode(['dist/dsh-plugin-dev.js', 'check', '--cwd', target, '--json'], { stdio: ['ignore', 'pipe', 'pipe'] })
69
+ let scaffoldOk = false
70
+ let scaffoldDetail = ''
71
+ try {
72
+ const parsed = JSON.parse(scaffoldCheck.stdout)
73
+ scaffoldOk = parsed.ok === true
74
+ if (!scaffoldOk) {
75
+ scaffoldDetail = (parsed.checks ?? [])
76
+ .filter((c) => c.status === 'fail')
77
+ .map((c) => c.id)
78
+ .join(', ')
79
+ }
80
+ } catch {
81
+ scaffoldDetail = 'unparseable --json output'
82
+ }
83
+ check(
84
+ scaffoldCheck.status === 0 && scaffoldOk,
85
+ `${lang} scaffold passes the checker (exit ${scaffoldCheck.status}${scaffoldDetail ? `; failing: ${scaffoldDetail}` : ''})`,
86
+ )
61
87
  }
62
88
 
63
89
  // 3b. Extension contract for the TS scaffold. `platform: node` makes tsdown