@boyingliu01/xp-gate 0.19.1 → 0.19.3

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.
Files changed (87) hide show
  1. package/gate-10.sh +105 -0
  2. package/hooks/pre-commit +24 -5
  3. package/lib/__tests__/npm-package.test.js +25 -0
  4. package/lib/mattpocock-license-verdict.json +1 -1
  5. package/mock-policy/AGENTS.md +3 -3
  6. package/mutation/AGENTS.md +3 -3
  7. package/package.json +2 -1
  8. package/plugins/claude-code/.claude-plugin/plugin.json +2 -2
  9. package/plugins/claude-code/skills/delphi-review/AGENTS.md +3 -3
  10. package/plugins/claude-code/skills/sprint-flow/AGENTS.md +3 -3
  11. package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +3 -3
  12. package/plugins/dsh/package.json +1 -1
  13. package/plugins/dsh/skills/delphi-review/AGENTS.md +3 -3
  14. package/plugins/dsh/skills/sprint-flow/AGENTS.md +3 -3
  15. package/plugins/dsh/skills/test-specification-alignment/AGENTS.md +3 -3
  16. package/plugins/opencode/package.json +1 -1
  17. package/plugins/opencode/skills/delphi-review/AGENTS.md +3 -3
  18. package/plugins/opencode/skills/sprint-flow/AGENTS.md +3 -3
  19. package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +3 -3
  20. package/plugins/qoder/plugin.json +2 -2
  21. package/plugins/qoder/skills/admin-template-guidelines/SKILL.md +6 -797
  22. package/plugins/qoder/skills/admin-template-guidelines/references/rule-1.md +91 -0
  23. package/plugins/qoder/skills/admin-template-guidelines/references/rule-2.md +155 -0
  24. package/plugins/qoder/skills/admin-template-guidelines/references/rule-3.md +70 -0
  25. package/plugins/qoder/skills/admin-template-guidelines/references/rule-4.md +154 -0
  26. package/plugins/qoder/skills/admin-template-guidelines/references/rule-5.md +126 -0
  27. package/plugins/qoder/skills/admin-template-guidelines/references/rule-6.md +180 -0
  28. package/plugins/qoder/skills/batch-grill-me/SKILL.md +15 -0
  29. package/plugins/qoder/skills/clipboard-vision/describe-clipboard.ps1 +115 -0
  30. package/plugins/qoder/skills/clipboard-vision/describe-clipboard.sh +161 -0
  31. package/plugins/qoder/skills/delphi-review/.delphi-config.json.example +61 -28
  32. package/plugins/qoder/skills/delphi-review/AGENTS.md +3 -3
  33. package/plugins/qoder/skills/delphi-review/INSTALL.md +97 -98
  34. package/plugins/qoder/skills/delphi-review/SKILL.md +77 -54
  35. package/plugins/qoder/skills/delphi-review/hooks.md +19 -0
  36. package/plugins/qoder/skills/delphi-review/opencode.json.delphi.example +6 -10
  37. package/plugins/qoder/skills/delphi-review/references/orchestrator-dispatch.md +64 -0
  38. package/plugins/qoder/skills/delphi-review/references/requirements.md +402 -0
  39. package/plugins/qoder/skills/delphi-review/references/round-templates.md +134 -0
  40. package/plugins/qoder/skills/delphi-review/scripts/verify-consensus.sh +43 -0
  41. package/plugins/qoder/skills/delphi-review/tools.md +29 -0
  42. package/plugins/qoder/skills/domain-modeling/ADR-FORMAT.md +64 -0
  43. package/plugins/qoder/skills/domain-modeling/CONTEXT-FORMAT.md +60 -0
  44. package/plugins/qoder/skills/domain-modeling/SKILL.md +74 -0
  45. package/plugins/qoder/skills/grill-with-docs/SKILL.md +7 -0
  46. package/plugins/qoder/skills/grilling/SKILL.md +12 -0
  47. package/plugins/qoder/skills/ralph-loop/SKILL.md +87 -428
  48. package/plugins/qoder/skills/ralph-loop/references/components.md +113 -0
  49. package/plugins/qoder/skills/ralph-loop/references/workflow-details.md +99 -0
  50. package/plugins/qoder/skills/sprint-flow/AGENTS.md +4 -4
  51. package/plugins/qoder/skills/sprint-flow/SKILL.md +255 -454
  52. package/plugins/qoder/skills/sprint-flow/__tests__/sprint-flow.test.cjs +457 -0
  53. package/plugins/qoder/skills/sprint-flow/hooks.md +18 -0
  54. package/plugins/qoder/skills/sprint-flow/references/components/memory.md +1 -1
  55. package/plugins/qoder/skills/sprint-flow/references/components/middleware.md +1 -1
  56. package/plugins/qoder/skills/sprint-flow/references/components/skill-invocations.md +21 -20
  57. package/plugins/qoder/skills/sprint-flow/references/components/tool-descriptions.md +22 -23
  58. package/plugins/qoder/skills/sprint-flow/references/force-levels.md +0 -1
  59. package/plugins/qoder/skills/sprint-flow/references/orchestration-rules.md +34 -27
  60. package/plugins/qoder/skills/sprint-flow/references/phase-1-prep.md +1 -1
  61. package/plugins/qoder/skills/sprint-flow/references/phase-2-design.md +157 -120
  62. package/plugins/qoder/skills/sprint-flow/references/phase-3-build.md +203 -2
  63. package/plugins/qoder/skills/sprint-flow/references/phase-4-verify.md +107 -34
  64. package/plugins/qoder/skills/sprint-flow/references/phase-5-ship.md +194 -12
  65. package/plugins/qoder/skills/sprint-flow/references/phase-6-close.md +139 -7
  66. package/plugins/qoder/skills/sprint-flow/references/phase-overview.md +486 -0
  67. package/plugins/qoder/skills/sprint-flow/scripts/verify-sprint-state.sh +38 -0
  68. package/plugins/qoder/skills/sprint-flow/tools.md +29 -0
  69. package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +3 -3
  70. package/plugins/qoder/skills/test-specification-alignment/SKILL.md +104 -601
  71. package/plugins/qoder/skills/to-issues/SKILL.md +1 -1
  72. package/principles/AGENTS.md +3 -3
  73. package/skills/delphi-review/AGENTS.md +3 -3
  74. package/skills/sprint-flow/AGENTS.md +3 -3
  75. package/skills/test-specification-alignment/AGENTS.md +3 -3
  76. package/plugins/qoder/skills/clipboard-vision/clipboard-vision/SKILL.md +0 -47
  77. package/plugins/qoder/skills/clipboard-vision/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +0 -65
  78. package/plugins/qoder/skills/clipboard-vision/clipboard-vision/__tests__/clipboard-vision-security.test.cjs +0 -185
  79. package/plugins/qoder/skills/clipboard-vision/clipboard-vision/__tests__/clipboard-vision.Tests.ps1 +0 -138
  80. package/plugins/qoder/skills/clipboard-vision/clipboard-vision/__tests__/clipboard-vision.test.cjs +0 -273
  81. package/plugins/qoder/skills/clipboard-vision/clipboard-vision/scripts/clipboard-vision.ps1 +0 -86
  82. package/plugins/qoder/skills/clipboard-vision/clipboard-vision/scripts/clipboard-vision.sh +0 -137
  83. package/plugins/qoder/skills/ralph-loop/references/components/memory.md +0 -55
  84. package/plugins/qoder/skills/ralph-loop/references/components/middleware.md +0 -54
  85. package/plugins/qoder/skills/ralph-loop/references/components/skill-invocations.md +0 -39
  86. package/plugins/qoder/skills/ralph-loop/references/components/system-prompt.md +0 -24
  87. package/plugins/qoder/skills/ralph-loop/references/components/tool-descriptions.md +0 -32
@@ -1,273 +0,0 @@
1
- /**
2
- * @test REQ-379 privacy-safe clipboard vision
3
- * @intent Verify one-shot clipboard image analysis enforces local-first privacy boundaries
4
- * @covers AC-379-01, AC-379-02, AC-379-03, AC-379-04, AC-379-05, AC-379-06
5
- */
6
- const fs = require('fs');
7
- const crypto = require('crypto');
8
- const os = require('os');
9
- const path = require('path');
10
- const { spawnSync } = require('child_process');
11
-
12
- const ROOT = path.resolve(__dirname, '..');
13
- const BASH_SCRIPT = path.join(ROOT, 'scripts', 'clipboard-vision.sh');
14
- const POWERSHELL_SCRIPT = path.join(ROOT, 'scripts', 'clipboard-vision.ps1');
15
- const SECRET = 'cv-secret-must-never-leak';
16
-
17
- function writeExecutable(file, content) {
18
- fs.writeFileSync(file, content, { mode: 0o755 });
19
- }
20
-
21
- function createFixture() {
22
- const root = fs.mkdtempSync(path.join(os.tmpdir(), 'clipboard-vision-test-'));
23
- const bin = path.join(root, 'bin');
24
- const temp = path.join(root, 'temp');
25
- fs.mkdirSync(bin);
26
- fs.mkdirSync(temp);
27
- const image = path.join(root, 'clipboard.png');
28
- fs.writeFileSync(image, Buffer.from('89504e470d0a1a0a', 'hex'));
29
- const clipboardCount = path.join(root, 'clipboard-count');
30
- const curlCount = path.join(root, 'curl-count');
31
- const curlArgs = path.join(root, 'curl-args');
32
- const curlBody = path.join(root, 'curl-body');
33
-
34
- writeExecutable(path.join(bin, 'pngpaste'), `#!/usr/bin/env bash
35
- set -euo pipefail
36
- printf x >> "${clipboardCount}"
37
- cp "${image}" "$1"
38
- `);
39
- writeExecutable(path.join(bin, 'curl'), `#!/usr/bin/env bash
40
- set -euo pipefail
41
- printf x >> "${curlCount}"
42
- printf '%s\n' "$@" > "${curlArgs}"
43
- while [ "$#" -gt 0 ]; do
44
- if [ "$1" = '--data-binary' ]; then cp "\${2#@}" "${curlBody}"; shift 2; continue; fi
45
- if [ "$1" = '--output' ]; then output="$2"; shift 2; continue; fi
46
- if [ "$1" = '--write-out' ]; then shift 2; continue; fi
47
- shift
48
- done
49
- if [ -n "\${CV_FAKE_RESPONSE:-}" ]; then
50
- printf '%s' "$CV_FAKE_RESPONSE" > "$output"
51
- else
52
- printf '%s' '{"choices":[{"message":{"content":"described safely"}}]}' > "$output"
53
- fi
54
- printf '%s' "\${CV_FAKE_STATUS:-200}"
55
- `);
56
-
57
- return { root, bin, temp, image, clipboardCount, curlCount, curlArgs, curlBody };
58
- }
59
-
60
- function run(fixture, args = [], env = {}) {
61
- return spawnSync('bash', [BASH_SCRIPT, ...args], {
62
- encoding: 'utf8',
63
- env: {
64
- ...process.env,
65
- PATH: `${fixture.bin}:${process.env.PATH}`,
66
- TMPDIR: fixture.temp,
67
- CLIPBOARD_VISION_MODEL: 'fixture-model',
68
- CLIPBOARD_VISION_API_KEY: SECRET,
69
- ...env,
70
- },
71
- });
72
- }
73
-
74
- function combined(result) {
75
- return `${result.stdout}${result.stderr}`;
76
- }
77
-
78
- function count(file) {
79
- return fs.existsSync(file) ? fs.readFileSync(file, 'utf8').length : 0;
80
- }
81
-
82
- function consentToken(hostname, model, base = `https://${hostname}/v1`) {
83
- const url = new URL(base);
84
- const scheme = url.protocol.slice(0, -1).toLowerCase();
85
- const normalizedHost = url.hostname.toLowerCase();
86
- const port = url.port || (scheme === 'https' ? '443' : '80');
87
- const displayHost = normalizedHost.includes(':') ? `[${normalizedHost}]` : normalizedHost;
88
- const endpoint = `${scheme}://${displayHost}:${port}${url.pathname.replace(/\/+$/, '')}/chat/completions`;
89
- return crypto.createHash('sha256').update(`${endpoint}\n${model}`, 'utf8').digest('hex');
90
- }
91
-
92
- function assertNoTempFiles(fixture) {
93
- expect(fs.readdirSync(fixture.temp)).toEqual([]);
94
- }
95
-
96
- describe('clipboard-vision Bash client', () => {
97
- let fixture;
98
-
99
- beforeEach(() => {
100
- fixture = createFixture();
101
- });
102
-
103
- afterEach(() => {
104
- fs.rmSync(fixture.root, { recursive: true, force: true });
105
- });
106
-
107
- it('fails without model configuration before reading clipboard or calling network', () => {
108
- const result = run(fixture, [], { CLIPBOARD_VISION_MODEL: '' });
109
-
110
- expect(result.status).not.toBe(0);
111
- expect(count(fixture.clipboardCount)).toBe(0);
112
- expect(count(fixture.curlCount)).toBe(0);
113
- });
114
-
115
- it('rejects a non-loopback endpoint in local mode before reading clipboard', () => {
116
- const result = run(fixture, [], { CLIPBOARD_VISION_API_BASE: 'https://vision.example.test/v1' });
117
-
118
- expect(result.status).not.toBe(0);
119
- expect(count(fixture.clipboardCount)).toBe(0);
120
- expect(count(fixture.curlCount)).toBe(0);
121
- });
122
-
123
- it('requires both remote opt-in and destination-bound confirmation', () => {
124
- const endpoint = { CLIPBOARD_VISION_API_BASE: 'https://vision.example.test/v1' };
125
- const optInOnly = run(fixture, ['--allow-remote'], endpoint);
126
- const confirmationOnly = run(fixture, ['--confirm-remote', consentToken('vision.example.test', 'fixture-model')], endpoint);
127
-
128
- expect(optInOnly.status).not.toBe(0);
129
- expect(confirmationOnly.status).not.toBe(0);
130
- expect(count(fixture.clipboardCount)).toBe(0);
131
- expect(count(fixture.curlCount)).toBe(0);
132
- });
133
-
134
- it('prints disclosure and expected token before rejecting missing confirmation', () => {
135
- const result = run(fixture, ['--allow-remote'], {
136
- CLIPBOARD_VISION_API_BASE: 'https://vision.example.test/v1',
137
- });
138
-
139
- expect(result.status).not.toBe(0);
140
- expect(result.stderr).toContain('vision.example.test');
141
- expect(result.stderr).toContain('fixture-model');
142
- expect(result.stderr.toLowerCase()).toContain('image');
143
- expect(result.stderr).toContain(consentToken('vision.example.test', 'fixture-model'));
144
- expect(count(fixture.clipboardCount)).toBe(0);
145
- expect(count(fixture.curlCount)).toBe(0);
146
- });
147
-
148
- it('accepts the confirmation token bound to normalized hostname and exact model', () => {
149
- const token = consentToken('vision.example.test', 'fixture-model');
150
- const result = run(fixture, ['--allow-remote', '--confirm-remote', token], {
151
- CLIPBOARD_VISION_API_BASE: 'https://VISION.Example.Test/v1',
152
- });
153
-
154
- expect(result.status, combined(result)).toBe(0);
155
- expect(count(fixture.clipboardCount)).toBe(1);
156
- expect(count(fixture.curlCount)).toBe(1);
157
- });
158
-
159
- it('rejects a token for a changed destination before clipboard or network', () => {
160
- const staleToken = consentToken('old.example.test', 'fixture-model');
161
- const result = run(fixture, ['--allow-remote', '--confirm-remote', staleToken], {
162
- CLIPBOARD_VISION_API_BASE: 'https://vision.example.test/v1',
163
- });
164
-
165
- expect(result.status).not.toBe(0);
166
- expect(count(fixture.clipboardCount)).toBe(0);
167
- expect(count(fixture.curlCount)).toBe(0);
168
- });
169
-
170
- it('rejects a token for a changed model before clipboard or network', () => {
171
- const staleToken = consentToken('vision.example.test', 'old-model');
172
- const result = run(fixture, ['--allow-remote', '--confirm-remote', staleToken], {
173
- CLIPBOARD_VISION_API_BASE: 'https://vision.example.test/v1',
174
- });
175
-
176
- expect(result.status).not.toBe(0);
177
- expect(count(fixture.clipboardCount)).toBe(0);
178
- expect(count(fixture.curlCount)).toBe(0);
179
- });
180
-
181
- it('never exposes the API key in output or request JSON', () => {
182
- const result = run(fixture);
183
-
184
- expect(result.status, combined(result)).toBe(0);
185
- expect(combined(result)).not.toContain(SECRET);
186
- expect(fs.readFileSync(fixture.curlBody, 'utf8')).not.toContain(SECRET);
187
- });
188
-
189
- it('rejects oversized clipboard images before network access', () => {
190
- fs.writeFileSync(fixture.image, Buffer.alloc(33));
191
- const result = run(fixture, [], { CLIPBOARD_VISION_MAX_BYTES: '32' });
192
-
193
- expect(result.status).not.toBe(0);
194
- expect(count(fixture.curlCount)).toBe(0);
195
- assertNoTempFiles(fixture);
196
- });
197
-
198
- it('removes temporary files after success', () => {
199
- const result = run(fixture);
200
-
201
- expect(result.status, combined(result)).toBe(0);
202
- expect(result.stdout.trim()).toBe('described safely');
203
- assertNoTempFiles(fixture);
204
- });
205
-
206
- it('redacts API failure bodies and removes temporary files after failure', () => {
207
- const response = `upstream body contains ${SECRET}`;
208
- const result = run(fixture, [], { CV_FAKE_STATUS: '500', CV_FAKE_RESPONSE: response });
209
-
210
- expect(result.status).not.toBe(0);
211
- expect(combined(result)).not.toContain(response);
212
- expect(combined(result)).not.toContain(SECRET);
213
- assertNoTempFiles(fixture);
214
- });
215
-
216
- it('performs exactly one clipboard read and one request per explicit invocation', () => {
217
- const result = run(fixture);
218
-
219
- expect(result.status, combined(result)).toBe(0);
220
- expect(count(fixture.clipboardCount)).toBe(1);
221
- expect(count(fixture.curlCount)).toBe(1);
222
- });
223
- });
224
-
225
- describe('clipboard-vision PowerShell contract', () => {
226
- it('keeps credentials in the environment and enforces bound consent, bounds, and cleanup', () => {
227
- const content = fs.readFileSync(POWERSHELL_SCRIPT, 'utf8');
228
-
229
- expect(content).toMatch(/\$env:CLIPBOARD_VISION_API_KEY/);
230
- expect(content).toMatch(/AllowRemote/);
231
- expect(content).toMatch(/ConfirmRemote/);
232
- expect(content).toMatch(/SHA256/i);
233
- expect(content).toMatch(/CLIPBOARD_VISION_MAX_BYTES/);
234
- expect(content).toMatch(/finally\s*\{/i);
235
- expect(content).toMatch(/Remove-Item/);
236
- expect(content).not.toMatch(/https:\/\/(?!127\.0\.0\.1|localhost|\[::1\])/);
237
- });
238
-
239
- it('disables HTTP redirects so loopback cannot forward the clipboard body remotely', () => {
240
- const content = fs.readFileSync(POWERSHELL_SCRIPT, 'utf8');
241
-
242
- expect(content).toMatch(/MaximumRedirection\s+0/);
243
- });
244
-
245
- it('validates an absolute HTTP(S) URI without credentials before disclosure or seams', () => {
246
- const content = fs.readFileSync(POWERSHELL_SCRIPT, 'utf8');
247
- const validationPosition = content.indexOf('IsAbsoluteUri');
248
- const disclosurePosition = content.indexOf('Remote disclosure');
249
- const clipboardPosition = content.indexOf('CLIPBOARD_VISION_TEST_IMAGE');
250
-
251
- expect(validationPosition).toBeGreaterThan(0);
252
- expect(content).toMatch(/Scheme[^\n]+https?/i);
253
- expect(content).toMatch(/UserInfo/);
254
- expect(validationPosition).toBeLessThan(disclosurePosition);
255
- expect(validationPosition).toBeLessThan(clipboardPosition);
256
- });
257
-
258
- it('ships a discoverable Windows-CI executable behavior harness', () => {
259
- expect(fs.existsSync(path.join(ROOT, '__tests__', 'clipboard-vision.Tests.ps1'))).toBe(true);
260
- });
261
-
262
- it('wires the discoverable PowerShell harness into windows-latest CI', () => {
263
- let repoRoot = ROOT;
264
- while (!fs.existsSync(path.join(repoRoot, '.github', 'workflows', 'cross-platform-ci.yml'))) {
265
- repoRoot = path.dirname(repoRoot);
266
- }
267
- const workflow = fs.readFileSync(path.join(repoRoot, '.github', 'workflows', 'cross-platform-ci.yml'), 'utf8');
268
-
269
- expect(workflow).toContain('windows-latest');
270
- expect(workflow).toContain('clipboard-vision.Tests.ps1');
271
- expect(workflow).toMatch(/shell:\s*pwsh/);
272
- });
273
- });
@@ -1,86 +0,0 @@
1
- [CmdletBinding()]
2
- param(
3
- [switch]$AllowRemote,
4
- [string]$ConfirmRemote
5
- )
6
-
7
- $ErrorActionPreference = 'Stop'
8
- $ApiBase = if ($env:CLIPBOARD_VISION_API_BASE) { $env:CLIPBOARD_VISION_API_BASE } else { 'http://127.0.0.1:11434/v1' }
9
- $Model = $env:CLIPBOARD_VISION_MODEL
10
- $ApiKey = $env:CLIPBOARD_VISION_API_KEY
11
- $MaxBytes = if ($env:CLIPBOARD_VISION_MAX_BYTES) { [long]$env:CLIPBOARD_VISION_MAX_BYTES } else { 10485760 }
12
-
13
- if (-not $Model) { throw 'CLIPBOARD_VISION_MODEL is required' }
14
- if ($MaxBytes -le 0) { throw 'CLIPBOARD_VISION_MAX_BYTES must be positive' }
15
- $Endpoint = $null
16
- if (-not [Uri]::TryCreate($ApiBase, [UriKind]::Absolute, [ref]$Endpoint) -or
17
- -not $Endpoint.IsAbsoluteUri -or
18
- $Endpoint.Scheme -notin @('http', 'https') -or
19
- $Endpoint.UserInfo.Length -ne 0 -or
20
- $Endpoint.Query.Length -ne 0 -or
21
- $Endpoint.Fragment.Length -ne 0) {
22
- throw 'CLIPBOARD_VISION_API_BASE must be an absolute HTTP(S) URI without credentials'
23
- }
24
- $Address = $null
25
- $LiteralAddress = [Net.IPAddress]::TryParse($Endpoint.Host.Trim('[', ']'), [ref]$Address)
26
- $Loopback = $LiteralAddress -and [Net.IPAddress]::IsLoopback($Address)
27
- $NormalizedHost = $Endpoint.Host.Trim('[', ']').ToLowerInvariant()
28
- $EffectivePort = if ($Endpoint.IsDefaultPort) { if ($Endpoint.Scheme -eq 'https') { 443 } else { 80 } } else { $Endpoint.Port }
29
- $DisplayHost = if ($NormalizedHost.Contains(':')) { "[$NormalizedHost]" } else { $NormalizedHost }
30
- $BasePath = $Endpoint.AbsolutePath.TrimEnd('/')
31
- $EffectiveEndpoint = "$($Endpoint.Scheme.ToLowerInvariant())://$DisplayHost`:$EffectivePort$BasePath/chat/completions"
32
- if (-not $Loopback) {
33
- $ConsentBytes = [Text.Encoding]::UTF8.GetBytes("$EffectiveEndpoint`n$Model")
34
- $Hasher = [Security.Cryptography.SHA256]::Create()
35
- try { $ExpectedConfirmation = ([BitConverter]::ToString($Hasher.ComputeHash($ConsentBytes))).Replace('-', '').ToLowerInvariant() } finally { $Hasher.Dispose() }
36
- [Console]::Error.WriteLine("Remote disclosure: clipboard image data will be sent to $EffectiveEndpoint using model $Model.")
37
- [Console]::Error.WriteLine("Confirmation token for this canonical endpoint and model: $ExpectedConfirmation")
38
- if (-not $AllowRemote -or $ConfirmRemote -cne $ExpectedConfirmation) {
39
- throw 'Remote endpoint requires -AllowRemote and the disclosed -ConfirmRemote token'
40
- }
41
- }
42
-
43
- $TempDirectory = Join-Path ([IO.Path]::GetTempPath()) ("clipboard-vision-" + [Guid]::NewGuid())
44
- $ImagePath = Join-Path $TempDirectory 'clipboard.png'
45
- try {
46
- New-Item -ItemType Directory -Path $TempDirectory | Out-Null
47
- if ($env:CLIPBOARD_VISION_TEST_IMAGE) {
48
- Copy-Item -LiteralPath $env:CLIPBOARD_VISION_TEST_IMAGE -Destination $ImagePath
49
- } else {
50
- Add-Type -AssemblyName System.Windows.Forms
51
- Add-Type -AssemblyName System.Drawing
52
- $Image = [Windows.Forms.Clipboard]::GetImage()
53
- if ($null -eq $Image) { throw 'Clipboard does not contain an image' }
54
- try { $Image.Save($ImagePath, [Drawing.Imaging.ImageFormat]::Png) } finally { $Image.Dispose() }
55
- }
56
- if ((Get-Item $ImagePath).Length -gt $MaxBytes) { throw "Clipboard image exceeds $MaxBytes bytes" }
57
-
58
- $Encoded = [Convert]::ToBase64String([IO.File]::ReadAllBytes($ImagePath))
59
- $Body = @{
60
- model = $Model
61
- messages = @(@{ role = 'user'; content = @(
62
- @{ type = 'text'; text = 'Describe this clipboard image.' },
63
- @{ type = 'image_url'; image_url = @{ url = "data:image/png;base64,$Encoded" } }
64
- ) })
65
- } | ConvertTo-Json -Depth 8 -Compress
66
- $Headers = @{}
67
- if ($ApiKey) { $Headers.Authorization = "Bearer $ApiKey" }
68
- try {
69
- if ($env:CLIPBOARD_VISION_TEST_HTTP_CAPTURE) {
70
- Set-Content -LiteralPath $env:CLIPBOARD_VISION_TEST_HTTP_CAPTURE -Value $Body -Encoding utf8
71
- if ($env:CV_TEST_HTTP_FAILURE) { throw 'test HTTP failure body secret' }
72
- if ($env:CV_TEST_MALFORMED_JSON) { $Response = $null }
73
- elseif ($env:CV_TEST_NON_STRING) { $Response = @{ choices = @(@{ message = @{ content = @{ value = 1 } } }) } }
74
- else { $Response = @{ choices = @(@{ message = @{ content = 'test description' } }) } }
75
- } else {
76
- $Response = Invoke-RestMethod -Method Post -Uri $EffectiveEndpoint -Headers $Headers -ContentType 'application/json' -Body $Body -MaximumRedirection 0
77
- }
78
- } catch {
79
- throw 'Vision API request failed (response body redacted)'
80
- }
81
- $Result = $Response.choices[0].message.content
82
- if ($Result -isnot [string]) { throw 'Invalid vision API response (body redacted)' }
83
- $Result
84
- } finally {
85
- Remove-Item -LiteralPath $TempDirectory -Recurse -Force -ErrorAction SilentlyContinue
86
- }
@@ -1,137 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
-
4
- api_base="${CLIPBOARD_VISION_API_BASE:-http://127.0.0.1:11434/v1}"
5
- model="${CLIPBOARD_VISION_MODEL:-}"
6
- api_key="${CLIPBOARD_VISION_API_KEY:-}"
7
- max_bytes="${CLIPBOARD_VISION_MAX_BYTES:-10485760}"
8
- allow_remote=false
9
- confirm_remote=""
10
- while [ "$#" -gt 0 ]; do
11
- case "$1" in
12
- --allow-remote) allow_remote=true; shift ;;
13
- --confirm-remote)
14
- [ "$#" -ge 2 ] || { printf '%s\n' 'Error: --confirm-remote requires the disclosed token' >&2; exit 2; }
15
- confirm_remote="$2"
16
- shift 2
17
- ;;
18
- --help)
19
- printf '%s\n' 'Usage: clipboard-vision.sh [--allow-remote --confirm-remote TOKEN]'
20
- exit 0
21
- ;;
22
- *) printf 'Error: unknown option: %s\n' "$1" >&2; exit 2 ;;
23
- esac
24
- done
25
-
26
- [ -n "$model" ] || { printf '%s\n' 'Error: CLIPBOARD_VISION_MODEL is required' >&2; exit 2; }
27
- case "$max_bytes" in *[!0-9]*|'') printf '%s\n' 'Error: CLIPBOARD_VISION_MAX_BYTES must be a positive integer' >&2; exit 2 ;; esac
28
- [ "$max_bytes" -gt 0 ] || { printf '%s\n' 'Error: CLIPBOARD_VISION_MAX_BYTES must be positive' >&2; exit 2; }
29
-
30
- endpoint_info=$(python3 - "$api_base" "$model" <<'PY'
31
- import hashlib
32
- import ipaddress
33
- import sys
34
- import urllib.parse
35
-
36
- url = urllib.parse.urlparse(sys.argv[1])
37
- if url.scheme not in {"http", "https"} or not url.hostname or url.username or url.password or url.query or url.fragment:
38
- raise SystemExit(2)
39
- try:
40
- loopback = ipaddress.ip_address(url.hostname).is_loopback
41
- except ValueError:
42
- loopback = False
43
- try:
44
- port = url.port or (443 if url.scheme == "https" else 80)
45
- except ValueError:
46
- raise SystemExit(2)
47
- print("local" if loopback else "remote")
48
- hostname = url.hostname.lower()
49
- print(hostname)
50
- display_host = f"[{hostname}]" if ":" in hostname else hostname
51
- base_path = url.path.rstrip("/")
52
- effective = f"{url.scheme.lower()}://{display_host}:{port}{base_path}/chat/completions"
53
- print(effective)
54
- print(hashlib.sha256(f"{effective}\n{sys.argv[2]}".encode()).hexdigest())
55
- PY
56
- ) || { printf '%s\n' 'Error: invalid CLIPBOARD_VISION_API_BASE' >&2; exit 2; }
57
- endpoint_kind=$(printf '%s\n' "$endpoint_info" | sed -n '1p')
58
- effective_endpoint=$(printf '%s\n' "$endpoint_info" | sed -n '3p')
59
- expected_confirmation=$(printf '%s\n' "$endpoint_info" | sed -n '4p')
60
-
61
- if [ "$endpoint_kind" = remote ]; then
62
- printf 'Remote disclosure: clipboard image data will be sent to %s using model %s.\n' "$effective_endpoint" "$model" >&2
63
- printf 'Confirmation token for this canonical endpoint and model: %s\n' "$expected_confirmation" >&2
64
- if [ "$allow_remote" != true ] || [ "$confirm_remote" != "$expected_confirmation" ]; then
65
- printf '%s\n' 'Error: remote endpoint requires --allow-remote and the disclosed --confirm-remote token' >&2
66
- exit 2
67
- fi
68
- fi
69
-
70
- work_dir=$(mktemp -d "${TMPDIR:-/tmp}/clipboard-vision.XXXXXX")
71
- image_file="$work_dir/clipboard.png"
72
- request_file="$work_dir/request.json"
73
- response_file="$work_dir/response.json"
74
- auth_file="$work_dir/auth-header"
75
- cleanup() { rm -rf -- "$work_dir"; }
76
- trap cleanup EXIT HUP INT TERM
77
-
78
- if command -v pngpaste >/dev/null 2>&1; then
79
- pngpaste "$image_file"
80
- elif command -v wl-paste >/dev/null 2>&1; then
81
- wl-paste --no-newline --type image/png > "$image_file"
82
- elif command -v import >/dev/null 2>&1; then
83
- import clipboard: "$image_file"
84
- else
85
- printf '%s\n' 'Error: install pngpaste, wl-paste, or ImageMagick import' >&2
86
- exit 2
87
- fi
88
-
89
- [ -s "$image_file" ] || { printf '%s\n' 'Error: clipboard does not contain an image' >&2; exit 2; }
90
- image_bytes=$(wc -c < "$image_file" | tr -d '[:space:]')
91
- [ "$image_bytes" -le "$max_bytes" ] || { printf 'Error: clipboard image exceeds %s bytes\n' "$max_bytes" >&2; exit 2; }
92
-
93
- python3 - "$image_file" "$request_file" "$model" <<'PY'
94
- import base64
95
- import json
96
- import pathlib
97
- import sys
98
-
99
- image = base64.b64encode(pathlib.Path(sys.argv[1]).read_bytes()).decode("ascii")
100
- request = {
101
- "model": sys.argv[3],
102
- "messages": [{"role": "user", "content": [
103
- {"type": "text", "text": "Describe this clipboard image."},
104
- {"type": "image_url", "image_url": {"url": "data:image/png;base64," + image}},
105
- ]}],
106
- }
107
- pathlib.Path(sys.argv[2]).write_text(json.dumps(request), encoding="utf-8")
108
- PY
109
-
110
- curl_args=(--silent --show-error --max-redirs 0 --output "$response_file" --write-out '%{http_code}' --header 'Content-Type: application/json' --data-binary "@$request_file")
111
- if [ -n "$api_key" ]; then
112
- case "$api_key" in *$'\n'*|*$'\r'*) printf '%s\n' 'Error: invalid CLIPBOARD_VISION_API_KEY' >&2; exit 2 ;; esac
113
- umask 077
114
- printf 'Authorization: Bearer %s\n' "$api_key" > "$auth_file"
115
- chmod 600 "$auth_file"
116
- curl_args+=(--header "@$auth_file")
117
- fi
118
- status=$(curl "${curl_args[@]}" "$effective_endpoint") || { printf '%s\n' 'Error: vision API request failed' >&2; exit 1; }
119
- case "$status" in 2??) ;; *) printf 'Error: vision API returned HTTP %s (body redacted)\n' "$status" >&2; exit 1 ;; esac
120
-
121
- result=$(python3 - "$response_file" <<'PY'
122
- import json
123
- import pathlib
124
- import sys
125
-
126
- try:
127
- data = json.loads(pathlib.Path(sys.argv[1]).read_text(encoding="utf-8"))
128
- value = data["choices"][0]["message"]["content"]
129
- if not isinstance(value, str):
130
- raise ValueError
131
- except (KeyError, IndexError, TypeError, ValueError, json.JSONDecodeError):
132
- raise SystemExit(2)
133
- print(value)
134
- PY
135
- ) || { printf '%s\n' 'Error: invalid vision API response (body redacted)' >&2; exit 1; }
136
-
137
- printf '%s\n' "$result"
@@ -1,55 +0,0 @@
1
- # memory.md — Ralph Loop 状态与记忆结构(AHE 组件分解)
2
-
3
- > **本文是 `skills/ralph-loop/SKILL.md` 中状态管理的 AHE 对齐展开。**
4
-
5
- ## 组件职责
6
-
7
- 定义 Ralph Loop 的进度持久化 Schema、learnings 分类结构。
8
-
9
- ## Progress Log Schema (`progress.log` — YAML)
10
-
11
- ```yaml
12
- req_progress:
13
- completed_count: 3
14
- total_count: 5
15
- current_req: REQ-XXX-004
16
- status: running|completed|blocked
17
- test_infra_status: generated|existing|skipped|fallback
18
-
19
- learnings:
20
- permanent:
21
- - pattern: "描述持久模式"
22
- evidence: "来源证据"
23
- contextual:
24
- - pattern: "描述当前 REQ 有效模式"
25
- expires_on: "过期条件"
26
-
27
- cost:
28
- req_number: 3
29
- tokens_used: 15000
30
- cumulative_tokens: 85000
31
- threshold: 200000
32
- ```
33
-
34
- ## Test Infra Status 字段
35
-
36
- | 值 | 含义 |
37
- |---|------|
38
- | `generated` | 本 REQ 首次生成 test-utils.ts |
39
- | `existing` | test-utils.ts 已存在且接口完整 |
40
- | `skipped` | 非首次 REQ,跳过 test-infra 检查(缓存结果)|
41
- | `fallback` | test-infra dispatch 失败,使用 inline 生成(含 warning)|
42
-
43
- ## Learnings 分类
44
-
45
- | 类型 | 生命周期 | 持久化 | 示例 |
46
- |------|---------|--------|------|
47
- | `permanent` | 跨所有 REQ | progress.log | "项目使用 ESLint strict 模式" |
48
- | `contextual` | 当前 REQ 内 | progress.log (过期标记) | "REQ 003 使用 Zod 验证 schema" |
49
-
50
- ## AHE 分类
51
-
52
- | 字段 | 值 |
53
- |------|---|
54
- | 组件类型 | Long-Term Memory |
55
- | 修改频率预期 | 低 |
@@ -1,54 +0,0 @@
1
- # middleware.md — Ralph Loop 迭代控制逻辑(AHE 组件分解)
2
-
3
- > **本文是 `skills/ralph-loop/SKILL.md` 中迭代流程的 AHE 对齐展开。**
4
-
5
- ## 组件职责
6
-
7
- 定义 Ralph Loop 的状态机:REQ 级迭代控制、熔断机制、中断续传逻辑。
8
-
9
- ## 状态机
10
-
11
- ```
12
- REQ N → 加载 learnings → 测试基础设施检查 → [infra missing?] → test_infra_dispatch
13
- │ │ │
14
- │ │ infra ready │ FAIL (max 2 retry)
15
- │ ▼ ▼
16
- │ TDD → 回归测试 ←───────────── BLOCK/fallback
17
- │ │
18
- │ PASS → REQ N+1
19
- │ FAIL → 重试 (max 3) → BLOCK → 用户决策
20
- │ ALL_REQS_COMPLETE → 结束,返回控制权给 sprint-flow orchestrator
21
- ```
22
-
23
- ## 测试基础设施状态机
24
-
25
- ```
26
- test_infra_check → [needs infra?] → test_infra_dispatch → FAIL → retry (max 2)
27
- │ │ │ │
28
- │ │ infra ready │ pass │ pass
29
- ▼ ▼ ▼ ▼
30
- in_progress test_infra_ready ─────────→ TDD → regression BLOCK/fallback
31
- ```
32
-
33
- ## 熔断机制
34
-
35
- | 触发条件 | 动作 |
36
- |---------|------|
37
- | TDD 失败 > 3 次 | BLOCK + 用户决策 |
38
- | Token 使用超阈值 | BLOCK + 用户决策 |
39
- | 回归测试失败 | 记录到 progress.log,通知 sprint-flow orchestrator |
40
- | test-infra dispatch 失败 (max 2 retry) | BLOCK 或 fallback inline 生成(记录 warning)|
41
-
42
- ## 暂停点
43
-
44
- | 暂停点 | 触发条件 | 恢复 |
45
- |--------|---------|------|
46
- | REQ 完成 | 自动暂停等待确认 | 确认后继续下一个 REQ |
47
- | 熔断 | 自动触发 | 用户决策后继续或放弃 |
48
-
49
- ## AHE 分类
50
-
51
- | 字段 | 值 |
52
- |------|---|
53
- | 组件类型 | Middleware |
54
- | 修改频率预期 | 高 |
@@ -1,39 +0,0 @@
1
- # skill-invocations.md — Ralph Loop 技能调用(AHE 组件分解)
2
-
3
- > **本文是 `skills/ralph-loop/SKILL.md` 中技能调用链的 AHE 对齐展开。**
4
-
5
- ## 组件职责
6
-
7
- 提供 Ralph Loop 在每个 REQ 执行中的技能调用链。
8
-
9
- ## 完整调用链
10
-
11
- | 步骤 | Skill | 来源 | 参数 |
12
- |------|-------|------|------|
13
- | 0 | 测试基础设施检查 | 内置 | 检查 test-utils.ts 存在性 + 接口契约(createTestApp, withTestDb)|
14
- | 1 | `test-driven-development` | superpowers | `--lang` 注入 + TDD 铁律 + Mock 边界(覆盖默认策略)|
15
- | 2 | `learn` | gstack | classification: permanent/contextual |
16
- | 3 | `requesting-code-review` | superpowers | REQ 完成后评审 |
17
-
18
- ## test-infra dispatch 节点
19
-
20
- 当 test-utils.ts 不存在或接口缺失时:
21
-
22
- ```
23
- task(category="unspecified-high", load_skills=["test-driven-development"], prompt="生成测试基础设施:createTestApp() + withTestDb()。TDD 铁律:先写测试再实现。")
24
- ```
25
-
26
- - retry max 2 次
27
- - 仍失败 → BLOCK 或 fallback inline 生成(记录 warning)
28
- - 成功后与业务代码合并为同一 commit
29
-
30
- ## 上游依赖
31
-
32
- 从 `specification.yaml` 获取 REQ 列表(由 `to-issues` skill 生成)
33
-
34
- ## AHE 分类
35
-
36
- | 字段 | 值 |
37
- |------|---|
38
- | 组件类型 | Skill Invocation Chain |
39
- | 修改频率预期 | 中 |
@@ -1,24 +0,0 @@
1
- # system-prompt.md — Ralph Loop 行为定义(AHE 组件分解)
2
-
3
- > **本文是 `skills/ralph-loop/SKILL.md` 核心原则的 AHE 对齐展开。**
4
-
5
- ## 核心职责
6
-
7
- 定义 Ralph Loop (Phase 2 BUILD 默认模式) 的行为边界:逐 REQ/切片 迭代构建,每个 REQ dispatch 独立 subagent,干净上下文,全量回归测试。
8
-
9
- ## 核心原则 (AHE 对齐)
10
-
11
- | # | 原则 | 说明 |
12
- |---|------|------|
13
- | 1 | 逐 REQ 迭代 | 一次处理一个 REQ/切片,干净上下文,避免上下文线性膨胀 |
14
- | 2 | Token 节约 | 比旧并行模式节约 40-67% token |
15
- | 3 | 全量回归 | 每个 REQ 完成后跑全量测试 |
16
- | 4 | 进度持久化 | progress.log 持久化 learnings/permanent/contextual |
17
- | 5 | 可中断续传 | 中断后可从最后完成 REQ 继续 |
18
-
19
- ## AHE 分类
20
-
21
- | 字段 | 值 |
22
- |------|---|
23
- | 组件类型 | System Prompt |
24
- | 修改频率预期 | 低 |