@boyingliu01/xp-gate 0.14.22 → 0.14.24
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/bin/xp-gate.js +8 -0
- package/lib/__tests__/init.test.js +77 -0
- package/lib/__tests__/phase-transition.test.js +174 -0
- package/lib/init.js +48 -0
- package/lib/phase-transition.js +211 -0
- package/mock-policy/AGENTS.md +3 -3
- package/mutation/AGENTS.md +3 -3
- package/package.json +3 -2
- package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
- package/plugins/claude-code/skills/delphi-review/AGENTS.md +3 -3
- package/plugins/claude-code/skills/delphi-review/SKILL.md +24 -4
- package/plugins/claude-code/skills/sprint-flow/AGENTS.md +3 -3
- package/plugins/claude-code/skills/sprint-flow/SKILL.md +20 -5
- package/plugins/claude-code/skills/sprint-flow/references/orchestration-rules.md +11 -16
- package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +3 -3
- package/plugins/opencode/package.json +1 -1
- package/plugins/opencode/skills/delphi-review/AGENTS.md +3 -3
- package/plugins/opencode/skills/delphi-review/SKILL.md +24 -4
- package/plugins/opencode/skills/sprint-flow/AGENTS.md +3 -3
- package/plugins/opencode/skills/sprint-flow/SKILL.md +20 -5
- package/plugins/opencode/skills/sprint-flow/references/orchestration-rules.md +11 -16
- package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +3 -3
- package/plugins/qoder/agents/delphi-architecture.md +48 -0
- package/plugins/qoder/agents/delphi-feasibility.md +48 -0
- package/plugins/qoder/agents/delphi-technical.md +48 -0
- package/plugins/qoder/plugin.json +1 -1
- package/plugins/qoder/skills/delphi-review/AGENTS.md +3 -3
- package/plugins/qoder/skills/delphi-review/SKILL.md +276 -167
- package/plugins/qoder/skills/sprint-flow/AGENTS.md +3 -3
- package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +3 -3
- package/principles/AGENTS.md +3 -3
- package/scripts/__tests__/sync-package-content.test.js +491 -0
- package/scripts/delphi-external-review.cjs +432 -0
- package/scripts/sync-package-content.js +429 -0
- package/skills/delphi-review/AGENTS.md +3 -3
- package/skills/delphi-review/SKILL.md +24 -4
- package/skills/sprint-flow/AGENTS.md +3 -3
- package/skills/sprint-flow/SKILL.md +20 -5
- package/skills/sprint-flow/references/orchestration-rules.md +11 -16
- package/skills/test-specification-alignment/AGENTS.md +3 -3
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
const PKG_ROOT = path.resolve(__dirname, '..');
|
|
8
|
+
const REPO_ROOT = path.resolve(PKG_ROOT, '..', '..');
|
|
9
|
+
|
|
10
|
+
const CORE_SKILLS = [
|
|
11
|
+
'admin-template-guidelines',
|
|
12
|
+
'delphi-review',
|
|
13
|
+
'improve-codebase-architecture',
|
|
14
|
+
'ralph-loop',
|
|
15
|
+
'sprint-flow',
|
|
16
|
+
'test-driven-development',
|
|
17
|
+
'test-specification-alignment',
|
|
18
|
+
'to-issues',
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
const PLUGINS = ['claude-code', 'opencode', 'qoder'];
|
|
22
|
+
|
|
23
|
+
// Plugins whose `skills/` subtree is gitignored (build artifact). For these,
|
|
24
|
+
// the source-of-truth skill content lives in repo-root `skills/` and must be
|
|
25
|
+
// injected during sync so the npm-package mirror is complete even on a clean
|
|
26
|
+
// checkout (e.g. CI Mirror Parity job). Plugins not listed here are expected
|
|
27
|
+
// to ship their own committed `skills/` subtree (e.g. `plugins/qoder/skills/`).
|
|
28
|
+
const PLUGINS_WITH_GITIGNORED_SKILLS = new Set(['claude-code', 'opencode']);
|
|
29
|
+
|
|
30
|
+
function rmrf(target) {
|
|
31
|
+
if (!fs.existsSync(target)) return;
|
|
32
|
+
fs.rmSync(target, { recursive: true, force: true });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const SKIP_DIRS = new Set(['node_modules', '.git', 'dist', 'coverage', '.opencode']);
|
|
36
|
+
const SKIP_FILES = new Set(['package-lock.json']);
|
|
37
|
+
const SKIP_FILE_SUFFIXES = ['.lock', '.js.map'];
|
|
38
|
+
|
|
39
|
+
function shouldSkipFile(name) {
|
|
40
|
+
if (SKIP_FILES.has(name)) return true;
|
|
41
|
+
return SKIP_FILE_SUFFIXES.some((suffix) => name.endsWith(suffix));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function copyDir(src, dest) {
|
|
45
|
+
if (!fs.existsSync(src)) {
|
|
46
|
+
console.error(`[sync] SKIP (missing): ${src}`);
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
50
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
51
|
+
for (const entry of entries) {
|
|
52
|
+
if (SKIP_DIRS.has(entry.name)) continue;
|
|
53
|
+
const srcPath = path.join(src, entry.name);
|
|
54
|
+
const destPath = path.join(dest, entry.name);
|
|
55
|
+
if (entry.isDirectory()) {
|
|
56
|
+
copyDir(srcPath, destPath);
|
|
57
|
+
} else if (entry.isFile()) {
|
|
58
|
+
if (shouldSkipFile(entry.name)) continue;
|
|
59
|
+
fs.copyFileSync(srcPath, destPath);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function syncSkills() {
|
|
66
|
+
const destRoot = path.join(PKG_ROOT, 'skills');
|
|
67
|
+
rmrf(destRoot);
|
|
68
|
+
fs.mkdirSync(destRoot, { recursive: true });
|
|
69
|
+
let copied = 0;
|
|
70
|
+
for (const name of CORE_SKILLS) {
|
|
71
|
+
const src = path.join(REPO_ROOT, 'skills', name);
|
|
72
|
+
const dest = path.join(destRoot, name);
|
|
73
|
+
if (copyDir(src, dest)) {
|
|
74
|
+
copied += 1;
|
|
75
|
+
console.error(`[sync] skills/${name}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return copied;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function injectCanonicalSkills(pluginDest) {
|
|
82
|
+
const skillsDest = path.join(pluginDest, 'skills');
|
|
83
|
+
rmrf(skillsDest);
|
|
84
|
+
fs.mkdirSync(skillsDest, { recursive: true });
|
|
85
|
+
for (const name of CORE_SKILLS) {
|
|
86
|
+
const src = path.join(REPO_ROOT, 'skills', name);
|
|
87
|
+
const dest = path.join(skillsDest, name);
|
|
88
|
+
if (!copyDir(src, dest)) {
|
|
89
|
+
console.error(`[sync] ERROR: missing canonical skill ${name}`);
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// tui-plugin.ts was removed in v0.13.0 (slimming). The TUI panel approach
|
|
96
|
+
// was abandoned after multiple unsuccessful integration attempts.
|
|
97
|
+
|
|
98
|
+
function syncPlugins() {
|
|
99
|
+
const destRoot = path.join(PKG_ROOT, 'plugins');
|
|
100
|
+
rmrf(destRoot);
|
|
101
|
+
fs.mkdirSync(destRoot, { recursive: true });
|
|
102
|
+
let copied = 0;
|
|
103
|
+
for (const name of PLUGINS) {
|
|
104
|
+
const src = path.join(REPO_ROOT, 'plugins', name);
|
|
105
|
+
const dest = path.join(destRoot, name);
|
|
106
|
+
if (copyDir(src, dest)) {
|
|
107
|
+
if (PLUGINS_WITH_GITIGNORED_SKILLS.has(name)) {
|
|
108
|
+
injectCanonicalSkills(dest);
|
|
109
|
+
}
|
|
110
|
+
// tui-plugin.ts was removed in v0.13.0 — no import rewriting needed.
|
|
111
|
+
copied += 1;
|
|
112
|
+
console.error(`[sync] plugins/${name}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return copied;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function syncScripts() {
|
|
119
|
+
const srcRoot = path.join(REPO_ROOT, 'scripts');
|
|
120
|
+
const destRoot = path.join(PKG_ROOT, 'scripts');
|
|
121
|
+
const scriptsToSync = ['delphi-external-review.cjs'];
|
|
122
|
+
let copied = 0;
|
|
123
|
+
for (const name of scriptsToSync) {
|
|
124
|
+
const src = path.join(srcRoot, name);
|
|
125
|
+
if (fs.existsSync(src)) {
|
|
126
|
+
fs.mkdirSync(destRoot, { recursive: true });
|
|
127
|
+
fs.copyFileSync(src, path.join(destRoot, name));
|
|
128
|
+
copied += 1;
|
|
129
|
+
console.error(`[sync] scripts/${name}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return copied;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function syncAdapters() {
|
|
136
|
+
const srcRoot = path.join(REPO_ROOT, 'githooks', 'adapters');
|
|
137
|
+
const destRoot = path.join(PKG_ROOT, 'adapters');
|
|
138
|
+
if (!fs.existsSync(srcRoot)) {
|
|
139
|
+
console.error(`[sync] SKIP adapters (missing): ${srcRoot}`);
|
|
140
|
+
return 0;
|
|
141
|
+
}
|
|
142
|
+
rmrf(destRoot);
|
|
143
|
+
fs.mkdirSync(destRoot, { recursive: true });
|
|
144
|
+
const entries = fs.readdirSync(srcRoot, { withFileTypes: true });
|
|
145
|
+
let copied = 0;
|
|
146
|
+
for (const entry of entries) {
|
|
147
|
+
const srcPath = path.join(srcRoot, entry.name);
|
|
148
|
+
const destPath = path.join(destRoot, entry.name);
|
|
149
|
+
if (entry.isDirectory()) {
|
|
150
|
+
copyDir(srcPath, destPath);
|
|
151
|
+
copied += 1;
|
|
152
|
+
} else if (entry.isFile() && entry.name.endsWith('.sh')) {
|
|
153
|
+
fs.copyFileSync(srcPath, destPath);
|
|
154
|
+
copied += 1;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
console.error(`[sync] adapters/ (${copied} entries)`);
|
|
158
|
+
|
|
159
|
+
// Sync gate scripts (gate-*.sh) from githooks/ to package root so they ship
|
|
160
|
+
// with the npm package and can be installed to the global adapter dir.
|
|
161
|
+
const githooksDir = path.join(REPO_ROOT, 'githooks');
|
|
162
|
+
const gateFiles = fs.readdirSync(githooksDir).filter(f =>
|
|
163
|
+
(f.startsWith('gate-') || f === 'sprint-gate.sh') && f.endsWith('.sh')
|
|
164
|
+
);
|
|
165
|
+
for (const f of gateFiles) {
|
|
166
|
+
fs.copyFileSync(path.join(githooksDir, f), path.join(PKG_ROOT, f));
|
|
167
|
+
}
|
|
168
|
+
if (gateFiles.length > 0) {
|
|
169
|
+
console.error(`[sync] gate scripts: ${gateFiles.join(', ')}`);
|
|
170
|
+
}
|
|
171
|
+
return copied + gateFiles.length;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function syncModules(moduleName) {
|
|
175
|
+
const src = path.join(REPO_ROOT, 'src', moduleName);
|
|
176
|
+
const dest = path.join(PKG_ROOT, moduleName);
|
|
177
|
+
if (!copyDir(src, dest)) {
|
|
178
|
+
console.error(`[sync] ERROR: missing module ${moduleName}`);
|
|
179
|
+
process.exit(1);
|
|
180
|
+
}
|
|
181
|
+
console.error(`[sync] ${moduleName}/`);
|
|
182
|
+
return 1;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function extractPreCommitGateNumbers(preCommitContent) {
|
|
186
|
+
const SCRIPT_META_GATES = new Set([]);
|
|
187
|
+
const gates = new Set();
|
|
188
|
+
for (const m of preCommitContent.matchAll(/Gate (\d+):/g)) {
|
|
189
|
+
if (!SCRIPT_META_GATES.has(m[1])) {
|
|
190
|
+
gates.add(m[1]);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return gates;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function extractReadmeGateNumbers(readmeContent, section) {
|
|
197
|
+
const gates = new Set();
|
|
198
|
+
for (const m of readmeContent.matchAll(section)) {
|
|
199
|
+
gates.add(m[1]);
|
|
200
|
+
}
|
|
201
|
+
return gates;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function compareGateSets(scriptGates, readmeGates, label, sortFn) {
|
|
205
|
+
const scriptOnly = [...scriptGates].filter((g) => !readmeGates.has(g));
|
|
206
|
+
const readmeOnly = [...readmeGates].filter((g) => !scriptGates.has(g));
|
|
207
|
+
|
|
208
|
+
if (scriptOnly.length > 0 || readmeOnly.length > 0) {
|
|
209
|
+
const hookFile = label.toLowerCase();
|
|
210
|
+
console.error(`[drift-check] ERROR: ${label} Gate table drift detected!`);
|
|
211
|
+
const sortedScript = [...scriptGates].sort(sortFn);
|
|
212
|
+
const sortedReadme = [...readmeGates].sort(sortFn);
|
|
213
|
+
console.error(` Script githooks/${hookFile} has ${scriptGates.size} gates: [${sortedScript.join(', ')}]`);
|
|
214
|
+
console.error(` README.md documents ${readmeGates.size} gates: [${sortedReadme.join(', ')}]`);
|
|
215
|
+
if (scriptOnly.length > 0) {
|
|
216
|
+
const sorted = sortFn ? scriptOnly.sort(sortFn) : scriptOnly;
|
|
217
|
+
console.error(` In script but NOT in README: Gate ${sorted.join(', Gate ')}`);
|
|
218
|
+
}
|
|
219
|
+
if (readmeOnly.length > 0) {
|
|
220
|
+
const sorted = sortFn ? readmeOnly.sort(sortFn) : readmeOnly;
|
|
221
|
+
console.error(` In README but NOT in script: Gate ${sorted.join(', Gate ')}`);
|
|
222
|
+
}
|
|
223
|
+
console.error(` Fix: Update README.md Gate table to match githooks/${hookFile}`);
|
|
224
|
+
}
|
|
225
|
+
return { scriptOnly, readmeOnly };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function checkPreCommitDrift(preCommitPath, readmePath) {
|
|
229
|
+
let scriptContent = '';
|
|
230
|
+
if (fs.existsSync(preCommitPath)) {
|
|
231
|
+
scriptContent = fs.readFileSync(preCommitPath, 'utf8');
|
|
232
|
+
} else {
|
|
233
|
+
console.error('[drift-check] WARN: githooks/pre-commit not found, skipping pre-commit check');
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const scriptGates = extractPreCommitGateNumbers(scriptContent);
|
|
238
|
+
|
|
239
|
+
let readmeContent = '';
|
|
240
|
+
if (fs.existsSync(readmePath)) {
|
|
241
|
+
readmeContent = fs.readFileSync(readmePath, 'utf8');
|
|
242
|
+
} else {
|
|
243
|
+
console.error('[drift-check] WARN: README.md not found, skipping drift check');
|
|
244
|
+
return true;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const readmeGates = extractReadmeGateNumbers(readmeContent, /\| Gate (\d+) \|/g);
|
|
248
|
+
|
|
249
|
+
if (scriptGates.size > 0 && readmeGates.size > 0) {
|
|
250
|
+
const numericSort = (a, b) => +a - +b;
|
|
251
|
+
const { scriptOnly, readmeOnly } = compareGateSets(scriptGates, readmeGates, 'Pre-commit', numericSort);
|
|
252
|
+
if (scriptOnly.length > 0 || readmeOnly.length > 0) {
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
console.error(`[drift-check] OK: pre-commit ${scriptGates.size} gates match README`);
|
|
256
|
+
}
|
|
257
|
+
return true;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function checkPrePushDrift(prePushPath, readmePath) {
|
|
261
|
+
let scriptContent = '';
|
|
262
|
+
if (fs.existsSync(prePushPath)) {
|
|
263
|
+
scriptContent = fs.readFileSync(prePushPath, 'utf8');
|
|
264
|
+
} else {
|
|
265
|
+
console.error('[drift-check] WARN: githooks/pre-push not found, skipping pre-push check');
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const scriptGates = extractReadmeGateNumbers(scriptContent, /[Gg][Aa][Tt][Ee] (M\d*)/g);
|
|
270
|
+
|
|
271
|
+
let readmeContent = '';
|
|
272
|
+
if (fs.existsSync(readmePath)) {
|
|
273
|
+
readmeContent = fs.readFileSync(readmePath, 'utf8');
|
|
274
|
+
} else {
|
|
275
|
+
return true;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const readmeGates = extractReadmeGateNumbers(readmeContent, /\| Gate (M\d*) \|/g);
|
|
279
|
+
|
|
280
|
+
if (scriptGates.size > 0 && readmeGates.size > 0) {
|
|
281
|
+
const { scriptOnly, readmeOnly } = compareGateSets(scriptGates, readmeGates, 'Pre-push');
|
|
282
|
+
if (scriptOnly.length > 0 || readmeOnly.length > 0) {
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
console.error(`[drift-check] OK: pre-push ${scriptGates.size} gates match README`);
|
|
286
|
+
}
|
|
287
|
+
return true;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Docs-drift check (REQ-TDD-005): compare Gate table in README.md against
|
|
292
|
+
* actual gate definitions in githooks/pre-commit and githooks/pre-push.
|
|
293
|
+
* Blocks publish if counts diverge.
|
|
294
|
+
*/
|
|
295
|
+
/**
|
|
296
|
+
* Adapter mirror drift check (REQ-329): compares githooks/adapters/ (source of truth)
|
|
297
|
+
* against src/npm-package/adapters/ (npm mirror). Content must be byte-identical;
|
|
298
|
+
* any mismatch blocks publish because syncAdapters() would overwrite the mirror.
|
|
299
|
+
*/
|
|
300
|
+
function checkAdapterDrift(srcRootPath, mirrorRootPath) {
|
|
301
|
+
const srcRoot = srcRootPath || path.join(REPO_ROOT, 'githooks', 'adapters');
|
|
302
|
+
const mirrorRoot = mirrorRootPath || path.join(PKG_ROOT, 'adapters');
|
|
303
|
+
|
|
304
|
+
if (!fs.existsSync(srcRoot)) {
|
|
305
|
+
console.error('[drift-check] WARN: githooks/adapters not found, skipping adapter drift check');
|
|
306
|
+
return true;
|
|
307
|
+
}
|
|
308
|
+
if (!fs.existsSync(mirrorRoot)) {
|
|
309
|
+
console.error('[drift-check] WARN: src/npm-package/adapters not found, skipping adapter drift check');
|
|
310
|
+
return true;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const crypto = require('crypto');
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Recursively compute a sorted, deterministic hash table for a directory.
|
|
317
|
+
* Keys are relative paths (POSIX separators); values are SHA-256 hex digests.
|
|
318
|
+
*/
|
|
319
|
+
function hashDirectory(root) {
|
|
320
|
+
const table = {};
|
|
321
|
+
function walk(dir, base) {
|
|
322
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
323
|
+
// Sort for determinism
|
|
324
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
325
|
+
for (const entry of entries) {
|
|
326
|
+
if (SKIP_DIRS.has(entry.name)) continue;
|
|
327
|
+
const full = path.join(dir, entry.name);
|
|
328
|
+
const rel = path.posix.join(base, entry.name);
|
|
329
|
+
if (entry.isDirectory()) {
|
|
330
|
+
walk(full, rel);
|
|
331
|
+
} else if (entry.isFile()) {
|
|
332
|
+
if (shouldSkipFile(entry.name)) continue;
|
|
333
|
+
const buf = fs.readFileSync(full);
|
|
334
|
+
table[rel] = crypto.createHash('sha256').update(buf).digest('hex');
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
walk(root, '');
|
|
339
|
+
return table;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const srcHashes = hashDirectory(srcRoot);
|
|
343
|
+
const mirrorHashes = hashDirectory(mirrorRoot);
|
|
344
|
+
|
|
345
|
+
// Check for files only in one side
|
|
346
|
+
const srcOnly = Object.keys(srcHashes).filter((f) => !(f in mirrorHashes));
|
|
347
|
+
const mirrorOnly = Object.keys(mirrorHashes).filter((f) => !(f in srcHashes));
|
|
348
|
+
|
|
349
|
+
// Check for content mismatches
|
|
350
|
+
const mismatched = [];
|
|
351
|
+
for (const f of Object.keys(srcHashes)) {
|
|
352
|
+
if (mirrorHashes[f] && srcHashes[f] !== mirrorHashes[f]) {
|
|
353
|
+
mismatched.push(f);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if (srcOnly.length > 0) {
|
|
358
|
+
console.error(`[drift-check] ERROR: ${srcOnly.length} file(s) in githooks/adapters/ missing from npm-package mirror:`);
|
|
359
|
+
for (const f of srcOnly) console.error(` - ${f}`);
|
|
360
|
+
}
|
|
361
|
+
if (mirrorOnly.length > 0) {
|
|
362
|
+
console.error(`[drift-check] ERROR: ${mirrorOnly.length} file(s) in npm-package mirror NOT in githooks/adapters/:`);
|
|
363
|
+
for (const f of mirrorOnly) console.error(` - ${f}`);
|
|
364
|
+
}
|
|
365
|
+
if (mismatched.length > 0) {
|
|
366
|
+
console.error(`[drift-check] ERROR: ${mismatched.length} adapter file(s) have drifted (SHA-256 mismatch):`);
|
|
367
|
+
for (const f of mismatched) console.error(` - ${f}`);
|
|
368
|
+
console.error('[drift-check] Fix: Run `node src/npm-package/scripts/sync-package-content.js` (or `npm pack`) to resync.');
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (srcOnly.length > 0 || mirrorOnly.length > 0 || mismatched.length > 0) {
|
|
372
|
+
return false;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const totalSrc = Object.keys(srcHashes).length;
|
|
376
|
+
const totalMirror = Object.keys(mirrorHashes).length;
|
|
377
|
+
console.error(`[drift-check] OK: ${totalSrc} adapter files match between githooks/adapters/ and npm-package mirror (${totalMirror} files)`);
|
|
378
|
+
return true;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function checkDocsDrift(preCommitPath, prePushPath, readmePath, agentsPath) {
|
|
382
|
+
const preCommitScript = preCommitPath || path.join(REPO_ROOT, 'githooks', 'pre-commit');
|
|
383
|
+
const prePushScript = prePushPath || path.join(REPO_ROOT, 'githooks', 'pre-push');
|
|
384
|
+
const readme = readmePath || path.join(REPO_ROOT, 'README.md');
|
|
385
|
+
|
|
386
|
+
const preCommitResult = checkPreCommitDrift(preCommitScript, readme);
|
|
387
|
+
if (preCommitResult === false) return false;
|
|
388
|
+
|
|
389
|
+
const prePushResult = checkPrePushDrift(prePushScript, readme);
|
|
390
|
+
if (prePushResult === false) return false;
|
|
391
|
+
|
|
392
|
+
return true;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function main() {
|
|
396
|
+
console.error(`[sync] repo root: ${REPO_ROOT}`);
|
|
397
|
+
console.error(`[sync] package root: ${PKG_ROOT}`);
|
|
398
|
+
if (checkDocsDrift() === false) {
|
|
399
|
+
process.exit(1);
|
|
400
|
+
}
|
|
401
|
+
if (checkAdapterDrift() === false) {
|
|
402
|
+
process.exit(1);
|
|
403
|
+
}
|
|
404
|
+
const skills = syncSkills();
|
|
405
|
+
const plugins = syncPlugins();
|
|
406
|
+
const scripts = syncScripts();
|
|
407
|
+
const adapters = syncAdapters();
|
|
408
|
+
const principles = syncModules('principles');
|
|
409
|
+
const mutation = syncModules('mutation');
|
|
410
|
+
const mockPolicy = syncModules('mock-policy');
|
|
411
|
+
const buildIntegrity = syncModules('build-integrity');
|
|
412
|
+
console.error(`[sync] done: ${skills} skill(s), ${plugins} plugin(s), ${scripts} script(s), ${adapters} adapter entries, ${principles + mutation + mockPolicy + buildIntegrity} module(s)`);
|
|
413
|
+
if (skills !== CORE_SKILLS.length) {
|
|
414
|
+
console.error(`[sync] ERROR: expected ${CORE_SKILLS.length} skills, copied ${skills}`);
|
|
415
|
+
process.exit(1);
|
|
416
|
+
}
|
|
417
|
+
if (plugins !== PLUGINS.length) {
|
|
418
|
+
console.error(`[sync] ERROR: expected ${PLUGINS.length} plugins, copied ${plugins}`);
|
|
419
|
+
process.exit(1);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// tui-plugin removed in v0.13.0 — validation skipped.
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
if (require.main !== module) {
|
|
426
|
+
module.exports = { checkDocsDrift, checkAdapterDrift };
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
main();
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
|
|
2
2
|
|
|
3
|
-
**Generated:** 2026-07-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-07-22
|
|
4
|
+
**Commit:** e331f2c
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.14.
|
|
6
|
+
**Version:** 0.14.24.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
Delphi Consensus Review — multi-round anonymous expert review (≥90% threshold, 3 experts from ≥2 providers, domestic models only). Supports design + code-walkthrough modes.
|
|
@@ -294,14 +294,34 @@ Each round MUST output a structured round marker:
|
|
|
294
294
|
| 2 专家(默认) | A(架构) + B(实现) | 代码变更、小型设计 |
|
|
295
295
|
| 3 专家 | A(架构) + B(实现) + C(可行性) | 架构决策、需求文档 |
|
|
296
296
|
|
|
297
|
-
### 模型选择策略(强制 —
|
|
298
|
-
|
|
299
|
-
**MUST 从 `opencode.json` 的 agent 配置中读取模型**,**严禁** hardcode 模型名称。
|
|
297
|
+
### 模型选择策略(强制 — 平台适配)
|
|
300
298
|
|
|
301
299
|
**关键原则**:
|
|
302
300
|
- ✅ 三个专家必须来自 **至少 2 家不同 provider**
|
|
303
|
-
- ❌ 禁止 hardcode
|
|
301
|
+
- ❌ 禁止 hardcode 模型名称
|
|
304
302
|
- ❌ 禁止三个专家全部使用同一 provider 的模型
|
|
303
|
+
- ❌ 禁止使用 Anthropic/OpenAI/Google 等国外模型
|
|
304
|
+
|
|
305
|
+
#### Qoder 平台(推荐 — Custom Agent 模式)
|
|
306
|
+
|
|
307
|
+
Qoder 环境下使用 **Custom Agent** 机制,每个专家是一个独立的 custom agent,配置不同的 Qoder 内置模型:
|
|
308
|
+
|
|
309
|
+
| 专家 | Agent 文件 | 模型 | Credits 费率 |
|
|
310
|
+
|------|-----------|------|-------------|
|
|
311
|
+
| Architecture (A) | `.qoder/agents/delphi-architecture.md` | Qwen3.7-Max | 0.5× |
|
|
312
|
+
| Technical (B) | `.qoder/agents/delphi-technical.md` | GLM-5.2 | 0.6× |
|
|
313
|
+
| Feasibility (C) | `.qoder/agents/delphi-feasibility.md` | DeepSeek-V4-Pro | 0.5× |
|
|
314
|
+
|
|
315
|
+
**执行方式**:通过 Agent tool 并行启动 3 个 subagent(type=GeneralPurpose),每个 agent 使用自己配置的模型独立评审,主 orchestrator 收集结果后计算共识。
|
|
316
|
+
|
|
317
|
+
**优势**:无需外部 API key,直接使用 Qoder Credits,模型由平台统一管理。
|
|
318
|
+
|
|
319
|
+
#### OpenCode 平台(External API 模式)
|
|
320
|
+
|
|
321
|
+
OpenCode 环境下通过 `opencode.json` 的 agent 配置 + `.delphi-config.json` 调用外部 API:
|
|
322
|
+
- **MUST 从 `opencode.json` 的 agent 配置中读取模型**
|
|
323
|
+
- 通过 `scripts/delphi-external-review.cjs` 调用各 provider 的 OpenAI 兼容 API
|
|
324
|
+
- 需要用户自行配置 API key(环境变量注入)
|
|
305
325
|
|
|
306
326
|
### 共识阈值
|
|
307
327
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/SPRINT-FLOW KNOWLEDGE BASE
|
|
2
2
|
|
|
3
|
-
**Generated:** 2026-07-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-07-22
|
|
4
|
+
**Commit:** e331f2c
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.14.
|
|
6
|
+
**Version:** 0.14.24.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
**6-phase** development pipeline (v2.0 compact redesign, Issue #290): PREP → DESIGN → BUILD → VERIFY → SHIP → CLOSE. Phase 3/6 BUILD default build mode is **ralph-loop** (REQ-level iteration, 40-67% token savings vs parallel). HARD-GATE between DESIGN (2/6) and BUILD (3/6): design must pass Delphi review (≥90% consensus) before any coding.
|
|
@@ -199,11 +199,26 @@ Every phase MUST output its header as the first line of that phase's output. For
|
|
|
199
199
|
3. NEVER merge phase output — each phase gets its own header
|
|
200
200
|
4. On `/sprint-flow` trigger, first line MUST output: `Sprint Flow: PREP → DESIGN → BUILD → VERIFY → SHIP → CLOSE`
|
|
201
201
|
5. Each phase completion MUST write Phase Summary to `.sprint-state/phase-outputs/phase-{N}-summary.md`
|
|
202
|
-
6. Each phase completion MUST
|
|
203
|
-
|
|
204
|
-
###
|
|
205
|
-
|
|
206
|
-
7.
|
|
202
|
+
6. Each phase completion MUST call `xp-gate phase-transition <N> <status> --render` — this programmatically updates `sprint-state.json` AND renders the ASCII dashboard in one command (resolves #338, #146)
|
|
203
|
+
|
|
204
|
+
### Phase Transition CLI (MANDATORY — replaces manual state updates)
|
|
205
|
+
|
|
206
|
+
7. **NEVER** manually write to `sprint-state.json` or manually render the dashboard from template. ALWAYS use:
|
|
207
|
+
```
|
|
208
|
+
npx xp-gate phase-transition <phase> <status> --render [--outputs '<json>']
|
|
209
|
+
```
|
|
210
|
+
- `<phase>`: 1-6
|
|
211
|
+
- `<status>`: `in_progress` | `completed` | `skipped` | `failed` | `paused`
|
|
212
|
+
- `--render`: Auto-renders ASCII progress dashboard after state update
|
|
213
|
+
- `--outputs '<json>'`: Optional JSON of phase outputs to record
|
|
214
|
+
|
|
215
|
+
Example (end of each phase):
|
|
216
|
+
```
|
|
217
|
+
npx xp-gate phase-transition 1 completed --render --outputs '{"estimate":"3 story points"}'
|
|
218
|
+
npx xp-gate phase-transition 2 in_progress --render
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
This is a **HARD GATE** — the orchestrator MUST invoke this CLI command. Text-level instructions to "update state" or "render dashboard" are DEPRECATED; the CLI is the single source of truth for state transitions and dashboard rendering.
|
|
207
222
|
|
|
208
223
|
### Phase Output Status Schema (MANDATORY per phase)
|
|
209
224
|
|
|
@@ -89,25 +89,20 @@
|
|
|
89
89
|
- 格式:YAML frontmatter + Markdown body(body ≤ 50 行)
|
|
90
90
|
- 大小限制:≤ 40,000 字符(≈ 10,000 tokens)
|
|
91
91
|
|
|
92
|
-
2.
|
|
93
|
-
|
|
94
|
-
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
92
|
+
2. **调用 phase-transition CLI**(替代手动更新 sprint-state.json + 手动渲染看板):
|
|
93
|
+
```
|
|
94
|
+
npx xp-gate phase-transition <phase> <status> --render [--outputs '<json>']
|
|
95
|
+
```
|
|
96
|
+
- Phase 开始时:`npx xp-gate phase-transition <N> in_progress --render`
|
|
97
|
+
- Phase 完成时:`npx xp-gate phase-transition <N> completed --render --outputs '{"key":"value"}'`
|
|
98
|
+
- Phase 跳过时:`npx xp-gate phase-transition <N> skipped --render`
|
|
99
|
+
- CLI 自动完成:更新 `sprint-state.json`(phase, phase_history, outputs)+ 渲染 ASCII 看板
|
|
100
|
+
- 看板规则:已完成 ✅ + 耗时,当前 🔄,待做 ⬜,跳过 ⏭️,失败 ❌
|
|
101
|
+
- 进度条:`[████▓░░░░░░] {pct}%`
|
|
102
|
+
- **禁止**手动写入 `sprint-state.json` 或手动调用 `render-sprint-progress.cjs`(已废弃)
|
|
99
103
|
|
|
100
104
|
3. **等待用户确认 checkpoint**(如适用)
|
|
101
105
|
|
|
102
|
-
4. **展示进度看板**:执行 `node scripts/render-sprint-progress.cjs` 渲染进度看板
|
|
103
|
-
- 脚本自动读取 `.sprint-state/sprint-state.json` 并输出 ASCII 进度看板
|
|
104
|
-
- 渲染规则:已完成阶段显示 ✅ + 耗时,当前阶段 🔄,待做 ⬜,跳过 ⏭️,失败 ❌
|
|
105
|
-
- 进度条:`[████▓░░░░░░] {pct}%`(已完成数/总阶段数 6)
|
|
106
|
-
- 下一步行动:根据当前阶段 + 状态,自动查找对应提示
|
|
107
|
-
- 输出物路径:列出 `outputs` 中已有的文件路径
|
|
108
|
-
- 时机:每个 Phase 完成后的 transition 阶段自动展示
|
|
109
|
-
- 向后兼容:旧版 `sprint-state.json` 缺少 `phase_history` 时,从 `phase` 字段推断状态
|
|
110
|
-
|
|
111
106
|
### Background Task Resume Protocol (MANDATORY — Issue #248)
|
|
112
107
|
|
|
113
108
|
After dispatching background agents with `run_in_background=true`:
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/TEST-SPECIFICATION-ALIGNMENT KNOWLEDGE BASE
|
|
2
2
|
|
|
3
|
-
**Generated:** 2026-07-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-07-22
|
|
4
|
+
**Commit:** e331f2c
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.14.
|
|
6
|
+
**Version:** 0.14.24.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.
|