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.
- package/README-es.md +28 -1
- package/README-hi.md +28 -1
- package/README-pt.md +28 -1
- package/README-zh.md +26 -1
- package/README.md +42 -12
- package/SKILL.md +21 -6
- package/dist/dsh-plugin-dev.js +95 -29
- package/guide/plugin-dev-guide.md +98 -2
- package/guide/quick-reference.es.md +2 -0
- package/guide/quick-reference.hi.md +2 -0
- package/guide/quick-reference.pt.md +2 -0
- package/index.d.ts +32 -0
- package/index.js +24 -9
- package/package.json +17 -9
- package/references/community-repo-deep-dive.md +90 -125
- package/references/harness-repo.md +19 -0
- package/references/mirror-scope-decision.md +72 -0
- package/references/sources.md +1 -0
- package/references/website-pages.md +5 -0
- package/scripts/archive-discussions.ps1 +162 -162
- package/scripts/check-docs-drift.ps1 +55 -55
- package/scripts/check-ps1-bom.mjs +68 -0
- package/scripts/check-readme-parity.mjs +149 -0
- package/scripts/check-readme-sync.mjs +14 -9
- package/scripts/download-community-articles.ps1 +188 -188
- package/scripts/download-community-repos.ps1 +232 -232
- package/scripts/download-sources.ps1 +195 -195
- package/scripts/gen-topic-snapshot.ps1 +91 -91
- package/scripts/install-skill.ps1 +63 -63
- package/scripts/sync-official-docs.ps1 +138 -138
- package/scripts/verify-artifacts.mjs +26 -0
- package/scripts/verify-kit.ps1 +197 -194
|
@@ -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
|