@boyingliu01/xp-gate 0.15.4 → 0.18.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.
Files changed (117) hide show
  1. package/bin/xp-gate.js +29 -1
  2. package/hooks/lib/now-ms.sh +33 -0
  3. package/hooks/pre-commit +63 -47
  4. package/hooks/sprint-gate.sh +291 -0
  5. package/lib/__tests__/check-version.test.js +122 -0
  6. package/lib/__tests__/doctor.test.js +13 -15
  7. package/lib/__tests__/gate-audit.test.ts +38 -0
  8. package/lib/__tests__/phase-transition.test.js +241 -1
  9. package/lib/__tests__/retro.test.js +131 -0
  10. package/lib/__tests__/sprint-status-rework.test.js +184 -0
  11. package/lib/check-version.js +9 -0
  12. package/lib/doctor-tui.js +79 -9
  13. package/lib/doctor.js +15 -3
  14. package/lib/gate-audit.ts +18 -7
  15. package/lib/gate-runner.js +77 -30
  16. package/lib/init.js +10 -0
  17. package/lib/mattpocock-license-verdict.json +7 -0
  18. package/lib/phase-transition.js +251 -1
  19. package/lib/retro.js +222 -0
  20. package/lib/shared-utils.js +7 -0
  21. package/lib/sprint-init.js +207 -0
  22. package/lib/sprint-state-migrator.js +1 -0
  23. package/lib/sprint-status.js +111 -0
  24. package/mock-policy/AGENTS.md +3 -3
  25. package/mutation/AGENTS.md +3 -3
  26. package/package.json +3 -2
  27. package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
  28. package/plugins/claude-code/skills/batch-grill-me/SKILL.md +15 -0
  29. package/plugins/claude-code/skills/delphi-review/AGENTS.md +3 -3
  30. package/plugins/claude-code/skills/delphi-review/SKILL.md +59 -4
  31. package/plugins/claude-code/skills/delphi-review/references/requirements.md +390 -0
  32. package/plugins/claude-code/skills/delphi-review/references/round-templates.md +67 -0
  33. package/plugins/claude-code/skills/domain-modeling/ADR-FORMAT.md +64 -0
  34. package/plugins/claude-code/skills/domain-modeling/CONTEXT-FORMAT.md +60 -0
  35. package/plugins/claude-code/skills/domain-modeling/SKILL.md +74 -0
  36. package/plugins/claude-code/skills/grill-with-docs/SKILL.md +7 -0
  37. package/plugins/claude-code/skills/grilling/SKILL.md +12 -0
  38. package/plugins/claude-code/skills/sprint-flow/AGENTS.md +3 -3
  39. package/plugins/claude-code/skills/sprint-flow/SKILL.md +168 -43
  40. package/plugins/claude-code/skills/sprint-flow/references/components/memory.md +1 -1
  41. package/plugins/claude-code/skills/sprint-flow/references/components/middleware.md +1 -1
  42. package/plugins/claude-code/skills/sprint-flow/references/components/skill-invocations.md +21 -20
  43. package/plugins/claude-code/skills/sprint-flow/references/components/tool-descriptions.md +22 -23
  44. package/plugins/claude-code/skills/sprint-flow/references/orchestration-rules.md +14 -12
  45. package/plugins/claude-code/skills/sprint-flow/references/phase-2-design.md +145 -133
  46. package/plugins/claude-code/skills/sprint-flow/references/phase-3-build.md +69 -2
  47. package/plugins/claude-code/skills/sprint-flow/references/phase-4-verify.md +93 -31
  48. package/plugins/claude-code/skills/sprint-flow/references/phase-5-ship.md +99 -14
  49. package/plugins/claude-code/skills/sprint-flow/references/phase-6-close.md +37 -5
  50. package/plugins/claude-code/skills/sprint-flow/references/phase-overview.md +58 -53
  51. package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +3 -3
  52. package/plugins/claude-code/skills/test-specification-alignment/SKILL.md +8 -3
  53. package/plugins/claude-code/skills/to-issues/SKILL.md +1 -1
  54. package/plugins/opencode/index.ts +1 -0
  55. package/plugins/opencode/package.json +1 -1
  56. package/plugins/opencode/skills/batch-grill-me/SKILL.md +15 -0
  57. package/plugins/opencode/skills/delphi-review/AGENTS.md +3 -3
  58. package/plugins/opencode/skills/delphi-review/SKILL.md +59 -4
  59. package/plugins/opencode/skills/delphi-review/references/requirements.md +390 -0
  60. package/plugins/opencode/skills/delphi-review/references/round-templates.md +67 -0
  61. package/plugins/opencode/skills/domain-modeling/ADR-FORMAT.md +64 -0
  62. package/plugins/opencode/skills/domain-modeling/CONTEXT-FORMAT.md +60 -0
  63. package/plugins/opencode/skills/domain-modeling/SKILL.md +74 -0
  64. package/plugins/opencode/skills/grill-with-docs/SKILL.md +7 -0
  65. package/plugins/opencode/skills/grilling/SKILL.md +12 -0
  66. package/plugins/opencode/skills/sprint-flow/AGENTS.md +3 -3
  67. package/plugins/opencode/skills/sprint-flow/SKILL.md +168 -43
  68. package/plugins/opencode/skills/sprint-flow/references/components/memory.md +1 -1
  69. package/plugins/opencode/skills/sprint-flow/references/components/middleware.md +1 -1
  70. package/plugins/opencode/skills/sprint-flow/references/components/skill-invocations.md +21 -20
  71. package/plugins/opencode/skills/sprint-flow/references/components/tool-descriptions.md +22 -23
  72. package/plugins/opencode/skills/sprint-flow/references/orchestration-rules.md +14 -12
  73. package/plugins/opencode/skills/sprint-flow/references/phase-2-design.md +145 -133
  74. package/plugins/opencode/skills/sprint-flow/references/phase-3-build.md +69 -2
  75. package/plugins/opencode/skills/sprint-flow/references/phase-4-verify.md +93 -31
  76. package/plugins/opencode/skills/sprint-flow/references/phase-5-ship.md +99 -14
  77. package/plugins/opencode/skills/sprint-flow/references/phase-6-close.md +37 -5
  78. package/plugins/opencode/skills/sprint-flow/references/phase-overview.md +58 -53
  79. package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +3 -3
  80. package/plugins/opencode/skills/test-specification-alignment/SKILL.md +8 -3
  81. package/plugins/opencode/skills/to-issues/SKILL.md +1 -1
  82. package/plugins/qoder/plugin.json +1 -1
  83. package/plugins/qoder/skills/delphi-review/AGENTS.md +3 -3
  84. package/plugins/qoder/skills/sprint-flow/AGENTS.md +3 -3
  85. package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +3 -3
  86. package/principles/AGENTS.md +3 -3
  87. package/scripts/__tests__/check-mattpocock-license.test.js +229 -0
  88. package/scripts/__tests__/sync-package-content.test.js +50 -0
  89. package/scripts/check-mattpocock-license.cjs +302 -0
  90. package/scripts/migrate-audit-anomalies.mjs +78 -0
  91. package/scripts/sync-package-content.js +52 -5
  92. package/skills/batch-grill-me/SKILL.md +15 -0
  93. package/skills/delphi-review/AGENTS.md +3 -3
  94. package/skills/delphi-review/SKILL.md +59 -4
  95. package/skills/delphi-review/references/requirements.md +390 -0
  96. package/skills/delphi-review/references/round-templates.md +67 -0
  97. package/skills/domain-modeling/ADR-FORMAT.md +64 -0
  98. package/skills/domain-modeling/CONTEXT-FORMAT.md +60 -0
  99. package/skills/domain-modeling/SKILL.md +74 -0
  100. package/skills/grill-with-docs/SKILL.md +7 -0
  101. package/skills/grilling/SKILL.md +12 -0
  102. package/skills/sprint-flow/AGENTS.md +3 -3
  103. package/skills/sprint-flow/SKILL.md +168 -43
  104. package/skills/sprint-flow/references/components/memory.md +1 -1
  105. package/skills/sprint-flow/references/components/middleware.md +1 -1
  106. package/skills/sprint-flow/references/components/skill-invocations.md +21 -20
  107. package/skills/sprint-flow/references/components/tool-descriptions.md +22 -23
  108. package/skills/sprint-flow/references/orchestration-rules.md +14 -12
  109. package/skills/sprint-flow/references/phase-2-design.md +145 -133
  110. package/skills/sprint-flow/references/phase-3-build.md +69 -2
  111. package/skills/sprint-flow/references/phase-4-verify.md +93 -31
  112. package/skills/sprint-flow/references/phase-5-ship.md +99 -14
  113. package/skills/sprint-flow/references/phase-6-close.md +37 -5
  114. package/skills/sprint-flow/references/phase-overview.md +58 -53
  115. package/skills/test-specification-alignment/AGENTS.md +3 -3
  116. package/skills/test-specification-alignment/SKILL.md +8 -3
  117. package/skills/to-issues/SKILL.md +1 -1
@@ -262,6 +262,56 @@ describe('syncGateScripts', () => {
262
262
  });
263
263
  });
264
264
 
265
+ /**
266
+ * @test REQ-450 sync-hooks
267
+ * @intent Verify syncHooks() copies githooks pre-commit, pre-push, adapter-common.sh,
268
+ * and lib/ subdirectory to src/npm-package/hooks/
269
+ * @covers AC-450-01, AC-450-02, AC-450-03, AC-450-04
270
+ */
271
+ describe('syncHooks', () => {
272
+ const WORKTREE_PATH = path.resolve(__dirname, '..', '..', '..', '..');
273
+ const PKG_ROOT = path.join(WORKTREE_PATH, 'src', 'npm-package');
274
+ const HOOKS_DIR = path.join(PKG_ROOT, 'hooks');
275
+ const syncScript = path.join(WORKTREE_PATH, 'src', 'npm-package', 'scripts', 'sync-package-content.js');
276
+ const nodeExe = process.execPath;
277
+
278
+ // Ensure hooks are re-synced once before tests run; share the result.
279
+ let syncOk = false;
280
+ beforeAll(() => {
281
+ const result = spawnSync(nodeExe, [syncScript], {
282
+ cwd: WORKTREE_PATH,
283
+ stdio: 'pipe',
284
+ timeout: 15000,
285
+ });
286
+ syncOk = result.status === 0;
287
+ if (!syncOk) {
288
+ console.error('sync stderr:', result.stderr?.toString() || '(empty)');
289
+ console.error('sync stdout:', result.stdout?.toString() || '(empty)');
290
+ }
291
+ }, 15000);
292
+
293
+ it('syncHooks copies pre-commit to hooks dir', () => {
294
+ expect(syncOk, 'sync script must exit 0').toBe(true);
295
+ expect(fs.existsSync(path.join(HOOKS_DIR, 'pre-commit')), 'pre-commit must exist').toBe(true);
296
+ });
297
+
298
+ it('syncHooks copies pre-push to hooks dir', () => {
299
+ expect(syncOk, 'sync script must exit 0').toBe(true);
300
+ expect(fs.existsSync(path.join(HOOKS_DIR, 'pre-push')), 'pre-push must exist').toBe(true);
301
+ });
302
+
303
+ it('syncHooks copies adapter-common.sh to hooks dir', () => {
304
+ expect(syncOk, 'sync script must exit 0').toBe(true);
305
+ expect(fs.existsSync(path.join(HOOKS_DIR, 'adapter-common.sh')), 'adapter-common.sh must exist').toBe(true);
306
+ });
307
+
308
+ it('syncHooks copies lib/now-ms.sh to hooks/lib dir', () => {
309
+ expect(syncOk, 'sync script must exit 0').toBe(true);
310
+ const libFile = path.join(HOOKS_DIR, 'lib', 'now-ms.sh');
311
+ expect(fs.existsSync(libFile), `lib/now-ms.sh must exist in hooks dir: ${libFile}`).toBe(true);
312
+ });
313
+ });
314
+
265
315
  /**
266
316
  * @test REQ-329 adapter-mirror-drift
267
317
  * @intent Verify checkAdapterDrift() detects file count and content mismatches
@@ -0,0 +1,302 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * C0 License Verification Gate (v0.18.0 Phase C0)
6
+ *
7
+ * Verifies that mattpocock/skills ships a permissive (MIT) license before the
8
+ * 4 built-in Matt Pocock skills (grilling, grill-with-docs, batch-grill-me,
9
+ * domain-modeling) are packed into the npm distribution. Design doc §6/§12/§16.4:
10
+ * a confirmed permissive license is a publish blocker; without it the skills
11
+ * must be clean-room rewritten from the functional spec instead of copied.
12
+ *
13
+ * Flow:
14
+ * 1. Fetch https://raw.githubusercontent.com/mattpocock/skills/main/LICENSE
15
+ * (10s timeout, one retry). On success, refresh the offline cache.
16
+ * 2. On network failure, fall back to the offline cache
17
+ * (.xp-gate/mattpocock-license.cache.md).
18
+ * 3. Validate the text is a compliant MIT license (regex: "MIT License"
19
+ * header + grant clause) and record SHA-256 as evidence.
20
+ * 4. Write a verdict JSON to lib/mattpocock-license-verdict.json.
21
+ *
22
+ * Exit codes:
23
+ * 0 PASS — MIT license confirmed (network or cache)
24
+ * 1 FAIL — license obtained but NOT a compliant MIT license
25
+ * 2 MISSING — license could not be obtained at all (triggers clean-room path)
26
+ *
27
+ * CLI flags:
28
+ * --offline Skip network; use the offline cache only.
29
+ * --refresh Re-fetch from network and update the cache
30
+ * (default flow already does this; explicit for clarity).
31
+ * --force-clean-room Record a clean-room rewrite decision to
32
+ * .xp-gate/clean-room-decision.json and exit 0 for this
33
+ * publish only (original skill text must NOT be copied).
34
+ *
35
+ * Env overrides (used by tests for hermetic execution):
36
+ * XPGATE_LICENSE_URL license URL to fetch
37
+ * XPGATE_LICENSE_CACHE offline cache file path
38
+ * XPGATE_LICENSE_VERDICT verdict JSON output path
39
+ * XPGATE_CLEAN_ROOM_DECISION clean-room decision JSON path
40
+ *
41
+ * Zero runtime dependencies (Node built-ins only).
42
+ */
43
+
44
+ const fs = require('fs');
45
+ const path = require('path');
46
+ const crypto = require('crypto');
47
+ const https = require('https');
48
+ const http = require('http');
49
+
50
+ const PKG_ROOT = path.resolve(__dirname, '..');
51
+ const REPO_ROOT = path.resolve(PKG_ROOT, '..', '..');
52
+
53
+ const DEFAULT_LICENSE_URL = 'https://raw.githubusercontent.com/mattpocock/skills/main/LICENSE';
54
+ const NETWORK_TIMEOUT_MS = 10000;
55
+ const MAX_RETRIES = 1;
56
+ const MAX_REDIRECTS = 3;
57
+
58
+ const LICENSE_URL = process.env.XPGATE_LICENSE_URL || DEFAULT_LICENSE_URL;
59
+ const CACHE_PATH = process.env.XPGATE_LICENSE_CACHE || path.join(REPO_ROOT, '.xp-gate', 'mattpocock-license.cache.md');
60
+ const VERDICT_PATH = process.env.XPGATE_LICENSE_VERDICT || path.join(PKG_ROOT, 'lib', 'mattpocock-license-verdict.json');
61
+ const CLEAN_ROOM_PATH = process.env.XPGATE_CLEAN_ROOM_DECISION || path.join(REPO_ROOT, '.xp-gate', 'clean-room-decision.json');
62
+
63
+ const EXIT_PASS = 0;
64
+ const EXIT_FAIL = 1; // license obtained but non-compliant
65
+ const EXIT_MISSING = 2; // license missing/unrecognizable → clean-room path
66
+
67
+ function sha256(text) {
68
+ return crypto.createHash('sha256').update(text, 'utf8').digest('hex');
69
+ }
70
+
71
+ /**
72
+ * Validate that license text is a compliant MIT license.
73
+ * Regex-based (tolerates copyright-year changes upstream) rather than a fixed
74
+ * hash, with the SHA-256 recorded separately as tamper evidence.
75
+ * @param {string} text
76
+ * @returns {{ valid: boolean, licenseType: string|null, reason: string }}
77
+ */
78
+ function validateLicense(text) {
79
+ if (typeof text !== 'string' || text.trim().length === 0) {
80
+ return { valid: false, licenseType: null, reason: 'license text is empty' };
81
+ }
82
+ const hasMitHeader = /MIT License/i.test(text);
83
+ const hasGrantClause = /Permission is hereby granted, free of charge/i.test(text);
84
+ const hasNoWarranty = /THE SOFTWARE IS PROVIDED "AS IS"/i.test(text);
85
+ const hasExpectedCopyright = /Copyright \(c\)\s+\d{4}\s+Matt Pocock/i.test(text);
86
+
87
+ if (hasMitHeader && hasGrantClause && hasNoWarranty) {
88
+ return {
89
+ valid: true,
90
+ licenseType: 'MIT',
91
+ reason: hasExpectedCopyright
92
+ ? 'MIT License with Matt Pocock copyright notice'
93
+ : 'MIT License (copyright holder differs from expected "Matt Pocock")',
94
+ };
95
+ }
96
+ return {
97
+ valid: false,
98
+ licenseType: null,
99
+ reason: 'text does not match MIT License terms (missing "MIT License" header, grant clause, or warranty disclaimer)',
100
+ };
101
+ }
102
+
103
+ /**
104
+ * Fetch a URL, following up to MAX_REDIRECTS redirects.
105
+ * Supports https: and http: (http only used by tests against a local server).
106
+ * @param {string} url
107
+ * @param {{ timeoutMs?: number, redirectsLeft?: number }} [options]
108
+ * @returns {Promise<string>}
109
+ */
110
+ function fetchUrl(url, options) {
111
+ const timeoutMs = (options && options.timeoutMs) || NETWORK_TIMEOUT_MS;
112
+ const redirectsLeft = options && typeof options.redirectsLeft === 'number' ? options.redirectsLeft : MAX_REDIRECTS;
113
+
114
+ return new Promise((resolve, reject) => {
115
+ let mod;
116
+ if (url.startsWith('https:')) mod = https;
117
+ else if (url.startsWith('http:')) mod = http;
118
+ else {
119
+ reject(new Error(`unsupported URL scheme: ${url}`));
120
+ return;
121
+ }
122
+
123
+ const req = mod.get(url, (res) => {
124
+ const status = res.statusCode || 0;
125
+ if (status >= 300 && status < 400 && res.headers.location && redirectsLeft > 0) {
126
+ res.resume();
127
+ const next = new URL(res.headers.location, url).toString();
128
+ fetchUrl(next, { timeoutMs, redirectsLeft: redirectsLeft - 1 }).then(resolve, reject);
129
+ return;
130
+ }
131
+ if (status !== 200) {
132
+ res.resume();
133
+ reject(new Error(`HTTP ${status} for ${url}`));
134
+ return;
135
+ }
136
+ const chunks = [];
137
+ res.on('data', (chunk) => chunks.push(chunk));
138
+ res.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
139
+ res.on('error', reject);
140
+ });
141
+ req.setTimeout(timeoutMs, () => {
142
+ req.destroy(new Error(`timeout after ${timeoutMs}ms fetching ${url}`));
143
+ });
144
+ req.on('error', reject);
145
+ });
146
+ }
147
+
148
+ /**
149
+ * Fetch with one retry after the initial attempt fails.
150
+ * @param {string} url
151
+ * @param {number} [retries]
152
+ * @returns {Promise<string>}
153
+ */
154
+ async function fetchWithRetry(url, retries) {
155
+ const attempts = (typeof retries === 'number' ? retries : MAX_RETRIES) + 1;
156
+ let lastErr;
157
+ for (let attempt = 1; attempt <= attempts; attempt++) {
158
+ try {
159
+ return await fetchUrl(url);
160
+ } catch (err) {
161
+ lastErr = err;
162
+ console.error(`[license-check] network attempt ${attempt}/${attempts} failed: ${err.message}`);
163
+ }
164
+ }
165
+ throw lastErr;
166
+ }
167
+
168
+ function writeJson(filePath, obj) {
169
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
170
+ fs.writeFileSync(filePath, `${JSON.stringify(obj, null, 2)}\n`, 'utf8');
171
+ }
172
+
173
+ function handleForceCleanRoom() {
174
+ const recordedAt = new Date().toISOString();
175
+ const decision = {
176
+ decision: 'clean-room-rewrite',
177
+ recorded_at: recordedAt,
178
+ note: 'mattpocock/skills LICENSE missing or non-compliant at publish time. '
179
+ + 'The 4 built-in skills (grilling, grill-with-docs, batch-grill-me, domain-modeling) '
180
+ + 'must be clean-room rewritten from the functional spec (interview discipline / '
181
+ + 'CONTEXT.md+ADR formats / batch decision protocol) — original expression must NOT be copied.',
182
+ };
183
+ writeJson(CLEAN_ROOM_PATH, decision);
184
+ writeJson(VERDICT_PATH, {
185
+ verdict: 'FAIL',
186
+ license_type: null,
187
+ sha256: null,
188
+ checked_at: recordedAt,
189
+ source: 'clean-room-decision',
190
+ clean_room: true,
191
+ });
192
+ console.error(`[license-check] clean-room decision recorded: ${CLEAN_ROOM_PATH}`);
193
+ console.error('[license-check] WARNING: publishing under clean-room override — verbatim skill text must NOT be shipped until the rewrite lands.');
194
+ process.exit(EXIT_PASS);
195
+ }
196
+
197
+ async function main() {
198
+ const args = process.argv.slice(2);
199
+ const offline = args.includes('--offline');
200
+ const refresh = args.includes('--refresh');
201
+
202
+ if (args.includes('--force-clean-room')) {
203
+ handleForceCleanRoom();
204
+ return;
205
+ }
206
+
207
+ let text = null;
208
+ let source = null;
209
+
210
+ if (offline) {
211
+ console.error('[license-check] --offline: skipping network fetch');
212
+ } else {
213
+ if (refresh) console.error('[license-check] --refresh: re-fetching license from network');
214
+ try {
215
+ text = await fetchWithRetry(LICENSE_URL);
216
+ source = 'network';
217
+ // Refresh the offline cache on every successful network fetch.
218
+ try {
219
+ fs.mkdirSync(path.dirname(CACHE_PATH), { recursive: true });
220
+ fs.writeFileSync(CACHE_PATH, text, 'utf8');
221
+ console.error(`[license-check] offline cache updated: ${CACHE_PATH}`);
222
+ } catch (cacheErr) {
223
+ console.error(`[license-check] WARN: failed to update offline cache: ${cacheErr.message}`);
224
+ }
225
+ } catch (netErr) {
226
+ console.error(`[license-check] network unavailable: ${netErr.message}`);
227
+ }
228
+ }
229
+
230
+ if (text === null && fs.existsSync(CACHE_PATH)) {
231
+ text = fs.readFileSync(CACHE_PATH, 'utf8');
232
+ source = 'cache';
233
+ console.error(`[license-check] using offline cache: ${CACHE_PATH}`);
234
+ }
235
+
236
+ const checkedAt = new Date().toISOString();
237
+
238
+ if (text === null) {
239
+ writeJson(VERDICT_PATH, {
240
+ verdict: 'FAIL',
241
+ license_type: null,
242
+ sha256: null,
243
+ checked_at: checkedAt,
244
+ source: 'none',
245
+ error: 'license missing: network fetch failed and no offline cache available',
246
+ });
247
+ console.error('[license-check] ERROR: mattpocock/skills LICENSE could not be obtained (network failed, no cache).');
248
+ console.error('[license-check] This is a publish blocker (design §6 C0 license gate).');
249
+ console.error('[license-check] Mitigation: start a clean-room rewrite of the 4 built-in skills from the functional spec,');
250
+ console.error('[license-check] or run `npm run license-check -- --force-clean-room` to record the clean-room rewrite path for this publish.');
251
+ process.exit(EXIT_MISSING);
252
+ }
253
+
254
+ const result = validateLicense(text);
255
+ const digest = sha256(text);
256
+
257
+ if (!result.valid) {
258
+ writeJson(VERDICT_PATH, {
259
+ verdict: 'FAIL',
260
+ license_type: result.licenseType,
261
+ sha256: digest,
262
+ checked_at: checkedAt,
263
+ source,
264
+ reason: result.reason,
265
+ });
266
+ console.error(`[license-check] FAIL: ${result.reason}`);
267
+ console.error(`[license-check] sha256: ${digest} (source: ${source})`);
268
+ console.error('[license-check] The license is NOT a compliant MIT license — verbatim redistribution of the skills is prohibited.');
269
+ console.error('[license-check] Mitigation: run `npm run license-check -- --force-clean-room` to record the clean-room rewrite path.');
270
+ process.exit(EXIT_FAIL);
271
+ }
272
+
273
+ writeJson(VERDICT_PATH, {
274
+ verdict: 'PASS',
275
+ license_type: 'MIT',
276
+ sha256: digest,
277
+ checked_at: checkedAt,
278
+ source,
279
+ });
280
+ console.error(`[license-check] PASS: MIT License confirmed (${result.reason})`);
281
+ console.error(`[license-check] sha256: ${digest}`);
282
+ console.error(`[license-check] source: ${source}`);
283
+ console.error(`[license-check] verdict written: ${VERDICT_PATH}`);
284
+ process.exit(EXIT_PASS);
285
+ }
286
+
287
+ if (require.main === module) {
288
+ main().catch((err) => {
289
+ console.error(`[license-check] unexpected error: ${err.message}`);
290
+ process.exit(EXIT_MISSING);
291
+ });
292
+ }
293
+
294
+ module.exports = {
295
+ validateLicense,
296
+ sha256,
297
+ fetchUrl,
298
+ fetchWithRetry,
299
+ EXIT_PASS,
300
+ EXIT_FAIL,
301
+ EXIT_MISSING,
302
+ };
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Issue #370: One-time migration to mark existing anomalous audit records.
4
+ *
5
+ * Scans .xp-gate/audit.jsonl for entries where duration_ms > max_duration_ms
6
+ * (default 7200000ms = 2h, configurable via .xp-gate-config.json audit.max_duration_ms)
7
+ * and adds `duration_anomaly: true` to those entries.
8
+ *
9
+ * Original duration_ms values are PRESERVED — never clamped or modified.
10
+ *
11
+ * Usage: node src/npm-package/scripts/migrate-audit-anomalies.mjs [--dry-run]
12
+ */
13
+ import { readFileSync, writeFileSync, existsSync } from 'fs';
14
+ import { join } from 'path';
15
+
16
+ const repoRoot = process.cwd();
17
+ const auditPath = join(repoRoot, '.xp-gate', 'audit.jsonl');
18
+ const configPath = join(repoRoot, '.xp-gate-config.json');
19
+ const dryRun = process.argv.includes('--dry-run');
20
+
21
+ // Read max_duration_ms from config (default 7200000)
22
+ let maxDurationMs = 7200000;
23
+ if (existsSync(configPath)) {
24
+ try {
25
+ const config = JSON.parse(readFileSync(configPath, 'utf8'));
26
+ if (config.audit && typeof config.audit.max_duration_ms === 'number') {
27
+ maxDurationMs = config.audit.max_duration_ms;
28
+ }
29
+ } catch {
30
+ // Use default
31
+ }
32
+ }
33
+
34
+ if (!existsSync(auditPath)) {
35
+ console.log(`No audit log found at ${auditPath} — nothing to migrate.`);
36
+ process.exit(0);
37
+ }
38
+
39
+ const content = readFileSync(auditPath, 'utf8');
40
+ const lines = content.split('\n');
41
+ let markedCount = 0;
42
+ let alreadyMarked = 0;
43
+ const updatedLines = [];
44
+
45
+ for (const line of lines) {
46
+ if (!line.trim()) {
47
+ updatedLines.push(line);
48
+ continue;
49
+ }
50
+ try {
51
+ const entry = JSON.parse(line);
52
+ if (entry.duration_ms > maxDurationMs) {
53
+ if (entry.duration_anomaly === true) {
54
+ alreadyMarked++;
55
+ } else {
56
+ entry.duration_anomaly = true;
57
+ markedCount++;
58
+ }
59
+ }
60
+ updatedLines.push(JSON.stringify(entry));
61
+ } catch {
62
+ // Preserve malformed lines as-is
63
+ updatedLines.push(line);
64
+ }
65
+ }
66
+
67
+ if (dryRun) {
68
+ console.log(`[DRY RUN] Would mark ${markedCount} records with duration_anomaly: true`);
69
+ console.log(`[DRY RUN] Already marked: ${alreadyMarked}`);
70
+ console.log(`[DRY RUN] Threshold: duration_ms > ${maxDurationMs}`);
71
+ } else {
72
+ writeFileSync(auditPath, updatedLines.join('\n'), 'utf8');
73
+ console.log(`Migration complete:`);
74
+ console.log(` Marked ${markedCount} records with duration_anomaly: true`);
75
+ console.log(` Already marked: ${alreadyMarked}`);
76
+ console.log(` Threshold: duration_ms > ${maxDurationMs}`);
77
+ console.log(` File: ${auditPath}`);
78
+ }
@@ -9,7 +9,11 @@ const REPO_ROOT = path.resolve(PKG_ROOT, '..', '..');
9
9
 
10
10
  const CORE_SKILLS = [
11
11
  'admin-template-guidelines',
12
+ 'batch-grill-me',
12
13
  'delphi-review',
14
+ 'domain-modeling',
15
+ 'grill-with-docs',
16
+ 'grilling',
13
17
  'improve-codebase-architecture',
14
18
  'ralph-loop',
15
19
  'sprint-flow',
@@ -172,6 +176,48 @@ function syncAdapters() {
172
176
  return copied + gateFiles.length;
173
177
  }
174
178
 
179
+ function syncHooks() {
180
+ const SRC = path.join(REPO_ROOT, 'githooks');
181
+ const DST = path.join(PKG_ROOT, 'hooks');
182
+ // In-place copy (no rmrf) to avoid race condition with parallel test readers.
183
+ fs.mkdirSync(DST, { recursive: true });
184
+
185
+ const HOOK_FILES = ['pre-commit', 'pre-push', 'adapter-common.sh'];
186
+ let copied = 0;
187
+
188
+ for (const name of HOOK_FILES) {
189
+ const srcPath = path.join(SRC, name);
190
+ const dstPath = path.join(DST, name);
191
+ if (fs.existsSync(srcPath)) {
192
+ fs.copyFileSync(srcPath, dstPath);
193
+ copied += 1;
194
+ }
195
+ }
196
+
197
+ // Copy lib/ subdirectory recursively
198
+ const libSrc = path.join(SRC, 'lib');
199
+ const libDst = path.join(DST, 'lib');
200
+ if (fs.existsSync(libSrc)) {
201
+ copyDir(libSrc, libDst);
202
+ copied += 1;
203
+ }
204
+
205
+ // Copy gate-*.sh files from githooks root to hooks/
206
+ const gateFiles = fs.readdirSync(SRC).filter(f =>
207
+ f.endsWith('.sh') && (f.startsWith('gate-') || f === 'sprint-gate.sh')
208
+ );
209
+ for (const f of gateFiles) {
210
+ fs.copyFileSync(path.join(SRC, f), path.join(DST, f));
211
+ }
212
+ if (gateFiles.length > 0) {
213
+ console.error(`[sync] hooks/ gate scripts: ${gateFiles.join(', ')}`);
214
+ copied += gateFiles.length;
215
+ }
216
+
217
+ console.error(`[sync] hooks/ (${copied} entries)`);
218
+ return copied;
219
+ }
220
+
175
221
  function syncModules(moduleName) {
176
222
  const src = path.join(REPO_ROOT, 'src', moduleName);
177
223
  const dest = path.join(PKG_ROOT, moduleName);
@@ -406,11 +452,12 @@ function main() {
406
452
  const plugins = syncPlugins();
407
453
  const scripts = syncScripts();
408
454
  const adapters = syncAdapters();
409
- const principles = syncModules('principles');
410
- const mutation = syncModules('mutation');
411
- const mockPolicy = syncModules('mock-policy');
412
- const buildIntegrity = syncModules('build-integrity');
413
- console.error(`[sync] done: ${skills} skill(s), ${plugins} plugin(s), ${scripts} script(s), ${adapters} adapter entries, ${principles + mutation + mockPolicy + buildIntegrity} module(s)`);
455
+ const hooks = syncHooks();
456
+ const principles = syncModules('principles');
457
+ const mutation = syncModules('mutation');
458
+ const mockPolicy = syncModules('mock-policy');
459
+ const buildIntegrity = syncModules('build-integrity');
460
+ console.error(`[sync] done: ${skills} skill(s), ${plugins} plugin(s), ${scripts} script(s), ${adapters} adapter entries, ${hooks} hook entries, ${principles + mutation + mockPolicy + buildIntegrity} module(s)`);
414
461
  if (skills !== CORE_SKILLS.length) {
415
462
  console.error(`[sync] ERROR: expected ${CORE_SKILLS.length} skills, copied ${skills}`);
416
463
  process.exit(1);
@@ -0,0 +1,15 @@
1
+ ---
2
+ name: batch-grill-me
3
+ description: A relentless interview that asks every frontier question at once, round by round.
4
+ disable-model-invocation: true
5
+ ---
6
+
7
+ Interview the user relentlessly until you reach a shared understanding. Map this as a **design tree**: every decision branches into the decisions that hang off it.
8
+
9
+ Work the tree in **rounds**. The **frontier** is every decision whose prerequisites are already settled — the questions you can ask *now* without guessing at answers you haven't heard yet. Ask the whole frontier in one round: number each question and give your recommended answer. Then wait for the user's answers before the next round.
10
+
11
+ Each round the user answers reshapes the tree — settled decisions push the frontier outward and unblock questions that depended on them. Recompute the frontier and ask the next round. A question whose answer depends on another question still open in this round belongs to a *later* round, not this one.
12
+
13
+ Finding *facts* is your job, never the user's. When a frontier question needs a fact from the environment (filesystem, tools, etc.), dispatch a sub-agent to find it — don't ask the user for anything you could look up yourself. Don't block on it: a running exploration is an unsettled prerequisite, so only the questions downstream of it wait for the sub-agent to report — ask the rest of the frontier now. The *decisions* are the user's — put each to them and wait.
14
+
15
+ The session is done when the frontier is empty: every branch of the design tree visited, nothing left silently assumed. Do not act on it until the user confirms you have reached a shared understanding.
@@ -1,9 +1,9 @@
1
1
  # SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-07-23
4
- **Commit:** 884f68c
3
+ **Generated:** 2026-07-25
4
+ **Commit:** 3150b22
5
5
  **Branch:** main
6
- **Version:** 0.15.4.0
6
+ **Version:** 0.18.0.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.