pwsh-guide 0.1.0

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.md ADDED
@@ -0,0 +1,47 @@
1
+ # pwsh-guide
2
+
3
+ 生成项目级 Agent Skill:探测当前 Windows/PowerShell 环境与本项目上下文,产出可靠的命令使用指南(`SKILL.md` + `references/`),供 Codex、Claude Code 等代理自动读取,减少命令失败、乱码与反复试错。
4
+
5
+ ## 原理
6
+
7
+ AI 代理在 Windows + PowerShell 下执行命令失败,本质是"生成即采样":模型先验以 bash 为主、缺少本机环境事实。`pwsh-guide` 把环境探测结果固化进 skill 的 references(惰性加载、不占上下文),并用命令铁律 + 失败诊断约束生成空间。
8
+
9
+ ## 安装(本地开发)
10
+
11
+ ```powershell
12
+ npm link
13
+ ```
14
+
15
+ ## 用法
16
+
17
+ ```powershell
18
+ # 在任意项目根目录运行
19
+ pwsh-guide init # 探测并生成 .agents/skills/pwsh-guide/
20
+ pwsh-guide refresh # 环境变化后重新探测,更新 references
21
+ pwsh-guide init --dry-run # 只输出探测 JSON,不写文件
22
+ ```
23
+
24
+ 生成的 skill 位于 `.agents/skills/pwsh-guide/`:
25
+
26
+ - `SKILL.md`:命令执行铁律、编码与乱码处理、失败分类诊断、禁止事项
27
+ - `references/environment.md`:探测到的本机环境事实(PS 版本/编码/执行策略/工具/项目上下文)
28
+ - `references/commands.md`:按探测到的工具动态裁剪的命令模板
29
+
30
+ ## 探测内容(只读)
31
+
32
+ - shell:PowerShell 版本/PSEdition/编码/代码页/执行策略/OS/用户与临时目录
33
+ - 工具:git、node、npm、npx、pnpm、yarn、bun、python、py、uv、pip、docker、make、rg、gh、pwsh、cargo、go 的可用性与版本
34
+ - 项目:git 根(向上查找,无则用当前目录)、标记文件、venv、package.json scripts、第一层子目录
35
+
36
+ ## 开发
37
+
38
+ ```text
39
+ bin/pwsh-guide.js CLI 入口(init / refresh / --dry-run)
40
+ src/probe.js spawn powershell.exe 执行探测并解析 JSON
41
+ src/detect.js 仓库根判定与目标目录解析
42
+ src/render.js 渲染 SKILL.md / environment.md / commands.md
43
+ scripts/probe.ps1 只读探测脚本(PS 5.1 兼容,输出 UTF-8 JSON)
44
+ templates/SKILL.md SKILL.md 模板({{占位符}} 由 render.js 填充)
45
+ ```
46
+
47
+ 变更记录由 OpenSpec 管理(`openspec/`),当前变更:`add-pwsh-guide`。
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env node
2
+ // pwsh-guide CLI 入口:init / refresh / --dry-run
3
+ // 用法:pwsh-guide init [--dry-run] | pwsh-guide refresh | pwsh-guide help
4
+ import { parseArgs } from 'node:util';
5
+ import fs from 'node:fs';
6
+ import path from 'node:path';
7
+ import { probeEnvironment } from '../src/probe.js';
8
+ import { resolveSkillDir } from '../src/detect.js';
9
+ import { renderSkill, renderEnvironment, renderCommands } from '../src/render.js';
10
+
11
+ const USAGE = `pwsh-guide - 生成项目级 PowerShell/cmd 操作指南 Agent Skill
12
+
13
+ 用法:
14
+ pwsh-guide init 探测环境并在 .agents/skills/pwsh-guide/ 生成 skill
15
+ pwsh-guide refresh 重新探测并更新 references(SKILL.md 保持不变)
16
+ pwsh-guide init --dry-run 仅输出探测结果,不写任何文件
17
+
18
+ 选项:
19
+ --dry-run 只输出探测 JSON(用于调试)
20
+ -h, --help 显示本帮助
21
+ `;
22
+
23
+ async function main() {
24
+ const { positionals, values } = parseArgs({
25
+ allowPositionals: true,
26
+ options: {
27
+ 'dry-run': { type: 'boolean', default: false },
28
+ help: { type: 'boolean', short: 'h', default: false },
29
+ },
30
+ });
31
+
32
+ const command = positionals[0] ?? 'help';
33
+ if (values.help || command === 'help') {
34
+ console.log(USAGE);
35
+ return;
36
+ }
37
+ if (command !== 'init' && command !== 'refresh') {
38
+ console.error(`[pwsh-guide] 未知命令: ${command}\n`);
39
+ console.error(USAGE);
40
+ process.exit(1);
41
+ }
42
+
43
+ const cwd = process.cwd();
44
+ const dryRun = values['dry-run'];
45
+
46
+ console.log('[pwsh-guide] 正在探测环境(只读,不修改任何文件)...');
47
+ const env = await probeEnvironment(cwd);
48
+
49
+ if (dryRun) {
50
+ console.log(JSON.stringify(env, null, 2));
51
+ return;
52
+ }
53
+
54
+ const skillDir = resolveSkillDir(cwd);
55
+ const skillExists = fs.existsSync(skillDir);
56
+ if (command === 'init' && skillExists) {
57
+ console.error(`[pwsh-guide] ${skillDir} 已存在,请改用 "pwsh-guide refresh" 更新,或先删除该目录后再 init。`);
58
+ process.exit(1);
59
+ }
60
+ if (command === 'refresh' && !skillExists) {
61
+ console.error(`[pwsh-guide] ${skillDir} 不存在,请先运行 "pwsh-guide init"。`);
62
+ process.exit(1);
63
+ }
64
+
65
+ const refsDir = path.join(skillDir, 'references');
66
+ fs.mkdirSync(refsDir, { recursive: true });
67
+
68
+ if (command === 'init') {
69
+ fs.writeFileSync(path.join(skillDir, 'SKILL.md'), renderSkill(env), 'utf8');
70
+ console.log(`[pwsh-guide] 已生成 ${path.join(skillDir, 'SKILL.md')}`);
71
+ }
72
+ fs.writeFileSync(path.join(refsDir, 'environment.md'), renderEnvironment(env), 'utf8');
73
+ fs.writeFileSync(path.join(refsDir, 'commands.md'), renderCommands(env), 'utf8');
74
+ console.log(`[pwsh-guide] 已更新 ${path.join(refsDir, 'environment.md')}`);
75
+ console.log(`[pwsh-guide] 已更新 ${path.join(refsDir, 'commands.md')}`);
76
+ console.log('\n完成。新会话中 Codex/Claude Code 会自动发现该 skill(项目级 .agents/skills/)。');
77
+ }
78
+
79
+ main().catch((err) => {
80
+ console.error(`[pwsh-guide] 失败: ${err.message}`);
81
+ process.exit(1);
82
+ });
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "pwsh-guide",
3
+ "version": "0.1.0",
4
+ "description": "Generate a project-level Agent Skill: probe the current Windows/PowerShell environment and produce a reliable command guide (SKILL.md + references) for agents such as Codex and Claude Code.",
5
+ "type": "module",
6
+ "bin": {
7
+ "pwsh-guide": "bin/pwsh-guide.js"
8
+ },
9
+ "engines": {
10
+ "node": ">=20"
11
+ },
12
+ "files": [
13
+ "bin",
14
+ "src",
15
+ "scripts",
16
+ "templates"
17
+ ],
18
+ "scripts": {
19
+ "test": "node --test"
20
+ },
21
+ "keywords": [
22
+ "agent-skills",
23
+ "powershell",
24
+ "windows",
25
+ "codex",
26
+ "claude-code"
27
+ ],
28
+ "license": "MIT"
29
+ }
@@ -0,0 +1,107 @@
1
+ # probe.ps1 - pwsh-guide 环境探测脚本(只读,不修改任何文件或系统设置)
2
+ # 用法:powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -File probe.ps1
3
+ # 输出:UTF-8 JSON 到 stdout(shell / tools / project 三部分)
4
+ # 兼容:Windows PowerShell 5.1+(不使用 PS7 新语法:??、&&、三元等)
5
+
6
+ $originalConsoleEncoding = [Console]::OutputEncoding.WebName
7
+ [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
8
+ $ErrorActionPreference = 'Continue'
9
+
10
+ function Get-ToolInfo {
11
+ param([string]$Name)
12
+ $cmd = Get-Command $Name -ErrorAction SilentlyContinue
13
+ if ($null -eq $cmd) { return $null }
14
+ $version = $null
15
+ try {
16
+ $raw = & $Name --version 2>$null | Select-Object -First 1
17
+ if ($null -ne $raw -and -not [string]::IsNullOrWhiteSpace([string]$raw)) { $version = [string]$raw }
18
+ } catch { $version = $null }
19
+ if ($null -eq $version -and $cmd.Source) {
20
+ try {
21
+ $v = (Get-Item $cmd.Source -ErrorAction Stop).VersionInfo.FileVersion
22
+ if ($null -ne $v -and -not [string]::IsNullOrWhiteSpace($v) -and $v -ne '0.0.0.0') { $version = $v }
23
+ } catch { $version = $null }
24
+ }
25
+ return [pscustomobject]@{ name = $Name; version = $version; source = $cmd.Source }
26
+ }
27
+
28
+ $executionPolicy = @{}
29
+ Get-ExecutionPolicy -List | ForEach-Object { $executionPolicy[[string]$_.Scope] = [string]$_.ExecutionPolicy }
30
+
31
+ $chcp = $null
32
+ try {
33
+ $chcpText = (& chcp 2>$null | Select-Object -First 1)
34
+ if ($chcpText -match '(\d+)') { $chcp = [int]$Matches[1] }
35
+ } catch { $chcp = $null }
36
+
37
+ $toolNames = @('git','node','npm','npx','pnpm','yarn','bun','python','py','uv','pip','docker','make','rg','gh','pwsh','cargo','go')
38
+ $tools = @()
39
+ foreach ($n in $toolNames) {
40
+ $info = Get-ToolInfo $n
41
+ if ($null -ne $info) { $tools += $info }
42
+ }
43
+
44
+ $cwd = (Get-Location).Path
45
+
46
+ $gitRoot = $null
47
+ if (Get-Command git -ErrorAction SilentlyContinue) {
48
+ try {
49
+ $line = git rev-parse --show-toplevel 2>$null | Select-Object -First 1
50
+ if ($line -and (Test-Path $line)) { $gitRoot = $line }
51
+ } catch { $gitRoot = $null }
52
+ }
53
+
54
+ $markerNames = @('package.json','pyproject.toml','requirements.txt','uv.lock','go.mod','Cargo.toml','pnpm-lock.yaml','yarn.lock','Dockerfile')
55
+ $subdirs = @(Get-ChildItem -Directory -ErrorAction SilentlyContinue | Where-Object { $_.Name -notin @('.git','.venv','venv','node_modules') } | Select-Object -ExpandProperty Name)
56
+ $foundMarkers = @()
57
+ foreach ($m in $markerNames) {
58
+ foreach ($d in (@('.') + $subdirs)) {
59
+ $p = Join-Path $cwd (Join-Path $d $m)
60
+ if (Test-Path $p) {
61
+ if ($d -eq '.') { $rel = $m } else { $rel = "$d\$m" }
62
+ $foundMarkers += $rel
63
+ }
64
+ }
65
+ }
66
+
67
+ $venv = $null
68
+ foreach ($d in @('.venv','venv')) {
69
+ if (Test-Path (Join-Path $cwd (Join-Path $d 'pyvenv.cfg'))) { $venv = $d; break }
70
+ }
71
+
72
+ $packageScripts = @{}
73
+ $pkgPath = Join-Path $cwd 'package.json'
74
+ if (Test-Path $pkgPath) {
75
+ try {
76
+ $pkg = Get-Content -Raw $pkgPath | ConvertFrom-Json
77
+ if ($null -ne $pkg.scripts) {
78
+ $pkg.scripts.PSObject.Properties | ForEach-Object { $packageScripts[$_.Name] = [string]$_.Value }
79
+ }
80
+ } catch { $packageScripts = @{} }
81
+ }
82
+
83
+ $result = [pscustomobject]@{
84
+ shell = [pscustomobject]@{
85
+ psVersion = $PSVersionTable.PSVersion.ToString()
86
+ psEdition = $PSVersionTable.PSEdition
87
+ hostName = $Host.Name
88
+ pwsh7Available = ($null -ne (Get-Command pwsh -ErrorAction SilentlyContinue))
89
+ osVersion = [System.Environment]::OSVersion.VersionString
90
+ consoleOutputEncoding = $originalConsoleEncoding
91
+ outputEncoding = $OutputEncoding.WebName
92
+ chcp = $chcp
93
+ executionPolicy = $executionPolicy
94
+ userProfile = $env:USERPROFILE
95
+ tempDir = $env:TEMP
96
+ }
97
+ tools = $tools
98
+ project = [pscustomobject]@{
99
+ cwd = $cwd
100
+ gitRoot = $gitRoot
101
+ markers = $foundMarkers
102
+ venv = $venv
103
+ packageScripts = $packageScripts
104
+ subdirs = $subdirs
105
+ }
106
+ }
107
+ $result | ConvertTo-Json -Depth 5
package/src/detect.js ADDED
@@ -0,0 +1,19 @@
1
+ // detect.js - 仓库根判定与 skill 目标目录解析
2
+ // 用法:findProjectRoot(cwd) -> string;resolveSkillDir(cwd) -> string
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+
6
+ export function findProjectRoot(startDir) {
7
+ const start = path.resolve(startDir);
8
+ let dir = start;
9
+ for (;;) {
10
+ if (fs.existsSync(path.join(dir, '.git'))) return dir;
11
+ const parent = path.dirname(dir);
12
+ if (parent === dir) return start; // 找不到 .git,降级为当前目录
13
+ dir = parent;
14
+ }
15
+ }
16
+
17
+ export function resolveSkillDir(cwd) {
18
+ return path.join(findProjectRoot(cwd), '.agents', 'skills', 'pwsh-guide');
19
+ }
package/src/probe.js ADDED
@@ -0,0 +1,41 @@
1
+ // probe.js - 调用 scripts/probe.ps1 探测环境并解析 JSON
2
+ // 用法:probeEnvironment(cwd) -> Promise<object>
3
+ import { spawn } from 'node:child_process';
4
+ import path from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+
7
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
8
+ const PROBE_SCRIPT = path.resolve(__dirname, '..', 'scripts', 'probe.ps1');
9
+
10
+ export async function probeEnvironment(cwd) {
11
+ return new Promise((resolve, reject) => {
12
+ const args = [
13
+ '-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass',
14
+ '-File', PROBE_SCRIPT,
15
+ ];
16
+ const child = spawn('powershell.exe', args, { cwd, shell: false, windowsHide: true });
17
+ let stdout = '';
18
+ let stderr = '';
19
+ child.stdout.setEncoding('utf8');
20
+ child.stderr.setEncoding('utf8');
21
+ child.stdout.on('data', (chunk) => { stdout += chunk; });
22
+ child.stderr.on('data', (chunk) => { stderr += chunk; });
23
+ child.on('error', (err) => reject(new Error(`无法启动 powershell.exe: ${err.message}`)));
24
+ child.on('close', (code) => {
25
+ if (code !== 0) {
26
+ reject(new Error(`probe.ps1 退出码 ${code}: ${stderr.trim()}`));
27
+ return;
28
+ }
29
+ const jsonStart = stdout.indexOf('{');
30
+ if (jsonStart === -1) {
31
+ reject(new Error(`探测输出不含 JSON:\n${stdout}\n${stderr}`));
32
+ return;
33
+ }
34
+ try {
35
+ resolve(JSON.parse(stdout.slice(jsonStart)));
36
+ } catch (err) {
37
+ reject(new Error(`解析探测 JSON 失败: ${err.message}`));
38
+ }
39
+ });
40
+ });
41
+ }
package/src/render.js ADDED
@@ -0,0 +1,245 @@
1
+ // render.js - 将探测 JSON 渲染为 SKILL.md / references/environment.md / references/commands.md
2
+ // 用法:renderSkill(env)、renderEnvironment(env)、renderCommands(env)
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+
7
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
8
+ const TEMPLATE = fs.readFileSync(path.resolve(__dirname, '..', 'templates', 'SKILL.md'), 'utf8');
9
+
10
+ function fill(template, values) {
11
+ let out = template;
12
+ for (const [key, value] of Object.entries(values)) {
13
+ out = out.split(`{{${key}}}`).join(String(value));
14
+ }
15
+ return out;
16
+ }
17
+
18
+ function getTool(env, name) {
19
+ return (env.tools || []).find((t) => t.name === name);
20
+ }
21
+
22
+ function toolVersion(env, name) {
23
+ const t = getTool(env, name);
24
+ return t && t.version ? String(t.version) : null;
25
+ }
26
+
27
+ function toolsSummary(env) {
28
+ const tools = (env.tools || []).filter((t) => t.version);
29
+ if (tools.length === 0) return '- 未探测到常用工具,请检查 PATH 后运行 pwsh-guide refresh。';
30
+ return tools.map((t) => `- ${t.name}: \`${t.version}\``).join('\n');
31
+ }
32
+
33
+ function executionPolicySummary(env) {
34
+ const ep = (env.shell && env.shell.executionPolicy) || {};
35
+ const parts = Object.entries(ep).map(([scope, policy]) => `${scope}=${policy}`);
36
+ return parts.length ? parts.join(', ') : '未获取';
37
+ }
38
+
39
+ function projectContextBlock(env) {
40
+ const p = env.project || {};
41
+ const lines = [];
42
+ lines.push(`- 当前目录:\`${p.cwd || '未知'}\``);
43
+ lines.push(`- git 仓库根:${p.gitRoot ? `\`${p.gitRoot}\`` : '未发现 .git(已用当前目录作为根)'}`);
44
+ lines.push(`- venv:${p.venv ? `\`${p.venv}\`` : '未发现'}`);
45
+ const markers = p.markers && p.markers.length ? p.markers.join(', ') : '未发现';
46
+ lines.push(`- 项目标记文件:${markers}`);
47
+ const scripts = p.packageScripts && Object.keys(p.packageScripts).length
48
+ ? Object.entries(p.packageScripts).map(([k, v]) => `${k}: ${v}`).join(';')
49
+ : '未发现 package.json scripts';
50
+ lines.push(`- package.json scripts:${scripts}`);
51
+ const subdirs = p.subdirs && p.subdirs.length ? p.subdirs.join(', ') : '(空)';
52
+ lines.push(`- 第一层子目录:${subdirs}`);
53
+ return lines.join('\n');
54
+ }
55
+
56
+ export function renderSkill(env) {
57
+ const shell = env.shell || {};
58
+ return fill(TEMPLATE, {
59
+ generatedAt: new Date().toISOString(),
60
+ psVersion: shell.psVersion || '未知',
61
+ consoleOutputEncoding: shell.consoleOutputEncoding || '未知',
62
+ chcp: shell.chcp ?? '未知',
63
+ executionPolicySummary: executionPolicySummary(env),
64
+ gitRoot: (env.project && env.project.gitRoot) || '未发现 .git',
65
+ cwd: (env.project && env.project.cwd) || process.cwd(),
66
+ toolsSummary: toolsSummary(env),
67
+ projectContext: projectContextBlock(env),
68
+ });
69
+ }
70
+
71
+ function renderEnvironment(env) {
72
+ const shell = env.shell || {};
73
+ const p = env.project || {};
74
+ const lines = [];
75
+ lines.push('# 环境事实(pwsh-guide 探测结果)');
76
+ lines.push('');
77
+ lines.push('> 本文件由 pwsh-guide 生成;环境变化后运行 `pwsh-guide refresh` 更新。');
78
+ lines.push('');
79
+ lines.push('## Shell 环境');
80
+ lines.push('');
81
+ lines.push('| 项 | 值 |');
82
+ lines.push('| --- | --- |');
83
+ lines.push(`| PowerShell 版本 | ${shell.psVersion ?? '-'} |`);
84
+ lines.push(`| PSEdition | ${shell.psEdition ?? '-'} |`);
85
+ lines.push(`| 主机 | ${shell.hostName ?? '-'} |`);
86
+ lines.push(`| pwsh 7 可用 | ${shell.pwsh7Available ? '是' : '否'} |`);
87
+ lines.push(`| OS | ${shell.osVersion ?? '-'} |`);
88
+ lines.push(`| 控制台输出编码 | ${shell.consoleOutputEncoding ?? '-'} |`);
89
+ lines.push(`| $OutputEncoding | ${shell.outputEncoding ?? '-'} |`);
90
+ lines.push(`| 代码页 chcp | ${shell.chcp ?? '-'} |`);
91
+ lines.push(`| 执行策略 | ${executionPolicySummary(env)} |`);
92
+ lines.push(`| 用户目录 | ${shell.userProfile ?? '-'} |`);
93
+ lines.push(`| 临时目录 | ${shell.tempDir ?? '-'} |`);
94
+ lines.push('');
95
+ lines.push('## 可用工具');
96
+ lines.push('');
97
+ const tools = (env.tools || []).filter((t) => t.version);
98
+ if (tools.length === 0) {
99
+ lines.push('未探测到常用工具。');
100
+ } else {
101
+ lines.push('| 工具 | 版本 | 路径 |');
102
+ lines.push('| --- | --- | --- |');
103
+ for (const t of tools) {
104
+ lines.push(`| ${t.name} | ${t.version} | \`${t.source || '-'}\` |`);
105
+ }
106
+ }
107
+ lines.push('');
108
+ lines.push('## 项目上下文');
109
+ lines.push('');
110
+ lines.push('| 项 | 值 |');
111
+ lines.push('| --- | --- |');
112
+ lines.push(`| 当前目录 | ${p.cwd ?? '-'} |`);
113
+ lines.push(`| git 仓库根 | ${p.gitRoot ?? '未发现 .git'} |`);
114
+ lines.push(`| venv | ${p.venv ?? '未发现'} |`);
115
+ lines.push(`| 标记文件 | ${p.markers && p.markers.length ? p.markers.join(', ') : '未发现'} |`);
116
+ const scriptsText = p.packageScripts && Object.keys(p.packageScripts).length
117
+ ? Object.entries(p.packageScripts).map(([k, v]) => `${k}: ${v}`).join('<br>')
118
+ : '未发现 package.json scripts';
119
+ lines.push(`| package.json scripts | ${scriptsText} |`);
120
+ lines.push(`| 第一层子目录 | ${p.subdirs && p.subdirs.length ? p.subdirs.join(', ') : '-'} |`);
121
+ return `${lines.join('\n')}\n`;
122
+ }
123
+
124
+ const COMMON_COMMANDS = [
125
+ '- 列出目录:`Get-ChildItem -Path . -Force`',
126
+ '- 递归查找文件:`Get-ChildItem -Recurse -Filter \'*.py\'`',
127
+ '- 文本搜索:`Select-String -Path \'**\\*.py\' -Pattern \'关键词\'`',
128
+ '- 读文件:`Get-Content -Path \'<file>\' -Encoding UTF8`',
129
+ '- 写文件:`Set-Content -Path \'<file>\' -Value \'<text>\' -Encoding UTF8`',
130
+ '- 判断路径存在:`Test-Path \'<path>\'`',
131
+ '- 环境变量读:`$env:NAME`;写:`$env:NAME = \'value\'`',
132
+ '- 进程列表:`Get-Process`;结束:`Stop-Process -Name <name> -Force`',
133
+ '- 端口占用:`Get-NetTCPConnection -LocalPort <port>`',
134
+ ];
135
+
136
+ const TOOL_ORDER = [
137
+ 'git', 'node', 'npm', 'npx', 'pnpm', 'yarn', 'bun',
138
+ 'python', 'py', 'uv', 'pip', 'docker', 'make', 'rg', 'gh', 'pwsh', 'cargo', 'go',
139
+ ];
140
+
141
+ const TOOL_COMMANDS = {
142
+ git: [
143
+ '- 状态:`git status`',
144
+ '- 未暂存差异:`git diff`;暂存差异:`git diff --cached`',
145
+ '- 最近提交:`git log --oneline -10`',
146
+ '- 当前分支:`git branch --show-current`',
147
+ '- 仓库根:`git rev-parse --show-toplevel`',
148
+ ],
149
+ node: [
150
+ '- 版本:`node --version`',
151
+ '- 运行脚本:`node <file>.js`',
152
+ '- 语法检查:`node --check <file>.js`',
153
+ ],
154
+ npm: [
155
+ '- 安装依赖(改 package.json 后):`npm install`',
156
+ '- 运行脚本:`npm run <script>`;查看脚本:`npm run`(不带参数)',
157
+ '- 安装依赖到项目:`npm install <pkg>`',
158
+ ],
159
+ npx: [
160
+ '- 临时运行包:`npx <pkg> <args>`',
161
+ '- 查看包信息:`npx <pkg> --help`',
162
+ ],
163
+ pnpm: [
164
+ '- 安装依赖:`pnpm install`',
165
+ '- 运行脚本:`pnpm run <script>`',
166
+ ],
167
+ yarn: [
168
+ '- 安装依赖:`yarn install`',
169
+ '- 运行脚本:`yarn run <script>`',
170
+ ],
171
+ bun: [
172
+ '- 安装依赖:`bun install`',
173
+ '- 运行脚本:`bun run <script>`',
174
+ ],
175
+ python: [
176
+ '- 版本:`python --version`',
177
+ '- 运行:`python <file>.py`',
178
+ '- 单行执行:`python -c \'print("hi")\'`',
179
+ ],
180
+ py: [
181
+ '- 版本:`py --version`(Windows Python 启动器)',
182
+ '- 运行:`py <file>.py`',
183
+ ],
184
+ uv: [
185
+ '- 版本:`uv --version`',
186
+ '- 创建虚拟环境:`uv venv`',
187
+ '- 在 venv 中运行:`uv run <cmd>`',
188
+ '- 添加依赖:`uv add <pkg>`',
189
+ ],
190
+ pip: [
191
+ '- 安装:`pip install <pkg>`',
192
+ '- 冻结依赖:`pip freeze`',
193
+ ],
194
+ docker: [
195
+ '- 版本:`docker --version`',
196
+ '- 容器列表:`docker ps`',
197
+ ],
198
+ make: [
199
+ '- 查看可用目标:`make -n` 或 `make help`',
200
+ '- 执行目标:`make <target>`',
201
+ ],
202
+ rg: [
203
+ '- 文本搜索:`rg \'<pattern>\' <path>`',
204
+ '- 列出文件:`rg --files`',
205
+ ],
206
+ gh: [
207
+ '- 仓库概览:`gh repo view`',
208
+ '- 创建 PR:`gh pr create`',
209
+ ],
210
+ pwsh: [
211
+ '- 版本:`pwsh -Version`',
212
+ '- 运行脚本:`pwsh -File <script>.ps1`',
213
+ ],
214
+ cargo: [
215
+ '- 构建:`cargo build`',
216
+ '- 测试:`cargo test`',
217
+ ],
218
+ go: [
219
+ '- 构建:`go build ./...`',
220
+ '- 测试:`go test ./...`',
221
+ ],
222
+ };
223
+
224
+ function renderCommands(env) {
225
+ const lines = [];
226
+ lines.push('# 命令模板(按工具)');
227
+ lines.push('');
228
+ lines.push('> 仅列出探测到可用的工具;`<...>` 为占位参数,执行前替换。');
229
+ lines.push('');
230
+ lines.push('## 通用操作(PowerShell 原生,始终可用)');
231
+ for (const c of COMMON_COMMANDS) lines.push(c);
232
+ lines.push('');
233
+ for (const tool of TOOL_ORDER) {
234
+ const info = getTool(env, tool);
235
+ if (!info || !info.version) continue;
236
+ const cmds = TOOL_COMMANDS[tool];
237
+ if (!cmds) continue;
238
+ lines.push(`## ${tool}(${info.version})`);
239
+ for (const c of cmds) lines.push(c);
240
+ lines.push('');
241
+ }
242
+ return `${lines.join('\n')}`;
243
+ }
244
+
245
+ export { renderEnvironment, renderCommands };
@@ -0,0 +1,61 @@
1
+ ---
2
+ name: pwsh-guide
3
+ description: 本项目的 Windows PowerShell/cmd 命令执行指南。需要执行 shell 命令、遇到 PowerShell 报错、中文乱码或不确定命令语法时使用;含本机环境事实、命令模板与失败诊断。
4
+ ---
5
+
6
+ # PowerShell/cmd 操作指南(本环境)
7
+
8
+ > 由 `pwsh-guide` 生成于 {{generatedAt}}。环境变化后运行 `pwsh-guide refresh` 更新。
9
+
10
+ ## 1. 执行命令前先确认
11
+
12
+ - Shell 是 `powershell.exe`(PS {{psVersion}}),**不是 bash**:禁止 `ls -la`、`grep`、`cat`、`rm -rf`、`export`。
13
+ - 控制台输出编码:{{consoleOutputEncoding}}(代码页 chcp {{chcp}})。
14
+ - 执行策略:{{executionPolicySummary}}。
15
+ - 当前目录:`{{cwd}}`;git 根:`{{gitRoot}}`。
16
+ - 完整环境事实见 `references/environment.md`;可用命令模板见 `references/commands.md`。
17
+
18
+ ## 2. 命令执行铁律
19
+
20
+ 1. 一条命令一次执行;禁止 `&&`、`||`、`;` 链式(PowerShell 5.1 不支持 `&&`)。
21
+ 2. 执行后检查退出状态:外部命令看 `$LASTEXITCODE`,cmdlet 看 `$?`,并查看 stderr。
22
+ 3. 路径含空格或中文时用单引号包裹:`Get-Content 'E:\path with 中文\file.txt'`。
23
+ 4. 环境变量读取 `$env:NAME`,写入 `$env:NAME = 'value'`(仅当前会话)。
24
+ 5. 只读操作优先;删除等危险操作先 `-WhatIf` 或展示计划再执行。
25
+ 6. 长命令优先写成 `.ps1` 再以 `-File` 执行,避免内联引号地狱。
26
+
27
+ ## 3. 常用命令模板
28
+
29
+ 可用工具与命令见 `references/commands.md`。本机探测到的工具:
30
+
31
+ {{toolsSummary}}
32
+
33
+ ## 4. 编码与乱码处理
34
+
35
+ - 临时切 UTF-8 输出:`[Console]::OutputEncoding = [System.Text.Encoding]::UTF8`。
36
+ - 切换代码页:`chcp 65001`(UTF-8)。
37
+ - 写文件指定编码:`Set-Content -Path <file> -Value <text> -Encoding UTF8`(PS 5.1 会写 BOM)。
38
+ - 读文件乱码:`Get-Content -Path <file> -Encoding UTF8`。
39
+ - 重定向中文输出优先 `Out-File -Encoding UTF8`,避免 `>` 按控制台编码写出。
40
+
41
+ ## 5. 失败分类诊断
42
+
43
+ | 症状 | 常见原因 | 处理 |
44
+ | --- | --- | --- |
45
+ | 命令找不到 | PATH 缺失 / 拼写错 | `Get-Command <name>` 确认;查看 `references/environment.md` 工具表 |
46
+ | 中文乱码 | 代码页与输出编码不匹配 | 按第 4 节切换 UTF-8 |
47
+ | 引号/转义报错 | 内联 `-Command` 引号地狱 | 改用 `-File` 脚本或参数数组 |
48
+ | 权限拒绝 | 执行策略 / ACL | `Get-ExecutionPolicy -List`;脚本加 `-ExecutionPolicy Bypass` |
49
+ | 路径找不到 | 空格/中文/反斜杠 | 单引号包裹;先 `Test-Path` |
50
+ | 程序假死 | 交互式命令等待输入 | 加 `-NonInteractive`,或 `echo y | <cmd>` |
51
+
52
+ ## 6. 禁止事项
53
+
54
+ - 禁止 bash 语法:`ls -la`、`grep`、`cat`、`rm -rf`、`export`、`&&`/`||`。
55
+ - 删除一律用 `Remove-Item -Path <path> -Recurse -Force`,且先确认路径再执行。
56
+ - 未读文件内容不得修改;不得批量格式化无关代码。
57
+ - 仓库 AGENTS.md 规则优先于本文件。
58
+
59
+ ## 7. 项目上下文(来自探测)
60
+
61
+ {{projectContext}}