@boyingliu01/xp-gate 0.20.2 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/adapters/gate-9.sh +142 -60
- package/adapters/python.sh +73 -6
- package/bin/xp-gate.js +2 -2
- package/gate-10.sh +107 -64
- package/gate-9.sh +142 -59
- package/hooks/gate-10.sh +107 -64
- package/hooks/gate-9.sh +142 -59
- package/hooks/lib/coverage-total.sh +72 -0
- package/hooks/pre-commit +128 -44
- package/hooks/pre-push +32 -0
- package/lib/__tests__/arch.test.js +403 -0
- package/lib/__tests__/doctor-469-hook-env.test.js +200 -0
- package/lib/arch.js +336 -49
- package/lib/doctor.js +319 -13
- package/lib/mattpocock-license-verdict.json +1 -1
- package/mock-policy/AGENTS.md +4 -4
- package/mutation/AGENTS.md +4 -4
- package/package.json +1 -1
- package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
- package/plugins/claude-code/skills/delphi-review/AGENTS.md +4 -4
- package/plugins/claude-code/skills/sprint-flow/AGENTS.md +4 -4
- package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +4 -4
- package/plugins/dsh/package.json +1 -1
- package/plugins/dsh/skills/delphi-review/AGENTS.md +4 -4
- package/plugins/dsh/skills/sprint-flow/AGENTS.md +4 -4
- package/plugins/dsh/skills/test-specification-alignment/AGENTS.md +4 -4
- package/plugins/dsh/src/command.test.ts +14 -2
- package/plugins/dsh/src/command.ts +8 -1
- package/plugins/dsh/src/index.test.ts +43 -26
- package/plugins/dsh/src/index.ts +164 -138
- package/plugins/opencode/README.md +5 -2
- package/plugins/opencode/index.ts +215 -164
- package/plugins/opencode/package.json +1 -1
- package/plugins/opencode/skills/delphi-review/AGENTS.md +4 -4
- package/plugins/opencode/skills/sprint-flow/AGENTS.md +4 -4
- package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +4 -4
- package/plugins/qoder/plugin.json +1 -1
- package/plugins/qoder/skills/delphi-review/AGENTS.md +4 -4
- package/plugins/qoder/skills/sprint-flow/AGENTS.md +4 -4
- package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +4 -4
- package/principles/AGENTS.md +4 -4
- package/skills/delphi-review/AGENTS.md +4 -4
- package/skills/sprint-flow/AGENTS.md +4 -4
- package/skills/test-specification-alignment/AGENTS.md +4 -4
|
@@ -319,175 +319,226 @@ function extractTitleFromText(text: string): string {
|
|
|
319
319
|
|
|
320
320
|
// ── Plugin definition ──
|
|
321
321
|
|
|
322
|
+
const SQLITE_TIMEOUT_MS = 5_000
|
|
323
|
+
|
|
324
|
+
// Boy Scout (#447): XpGatePlugin used to inline five tool({...}) definitions
|
|
325
|
+
// (191 lines, 7-8 brace levels). Each tool now lives in its own factory;
|
|
326
|
+
// XpGatePlugin is pure wiring.
|
|
327
|
+
function gateCheckTool(directory: string) {
|
|
328
|
+
return tool({
|
|
329
|
+
description: "Run xp-gate quality gates (Gate 4 + Gate 6) on a file or directory. Prefers global xp-gate CLI; falls back to direct checker source.",
|
|
330
|
+
args: {
|
|
331
|
+
path: z.string().describe("File or directory path (absolute or relative to workspace)"),
|
|
332
|
+
gates: z.array(z.string()).optional().describe("Optional gate subset (e.g. ['principles', 'arch'])"),
|
|
333
|
+
},
|
|
334
|
+
async execute(args, ctx) {
|
|
335
|
+
const cwd = ctx.directory || directory
|
|
336
|
+
const target = args.path.startsWith("/") ? args.path : `${cwd}/${args.path}`
|
|
337
|
+
const gatesFlag = args.gates?.length ? ` --gates ${args.gates.join(",")}` : ""
|
|
338
|
+
const result = await runXpGate(`check "${target}"${gatesFlag}`, cwd)
|
|
339
|
+
if (result) {
|
|
340
|
+
const upgrade = await getUpgradeSuggestion(cwd)
|
|
341
|
+
return upgrade ? `${result}\n${upgrade}` : result
|
|
342
|
+
}
|
|
343
|
+
const cmd = `node ${directory}/src/npm-package/bin/xp-gate.js check "${target}"${gatesFlag}`
|
|
344
|
+
return runCmd(cmd, cwd)
|
|
345
|
+
},
|
|
346
|
+
})
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function gatePrinciplesTool(directory: string) {
|
|
350
|
+
return tool({
|
|
351
|
+
description: "Run Clean Code + SOLID principles checker (Gate 4 standalone) on a file or directory.",
|
|
352
|
+
args: {
|
|
353
|
+
path: z.string().describe("Source file or directory path to check"),
|
|
354
|
+
},
|
|
355
|
+
async execute(args, ctx) {
|
|
356
|
+
const cwd = ctx.directory || directory
|
|
357
|
+
const target = args.path.startsWith("/") ? args.path : `${cwd}/${args.path}`
|
|
358
|
+
const result = await runXpGate(`principles "${target}"`, cwd)
|
|
359
|
+
if (result) {
|
|
360
|
+
const upgrade = await getUpgradeSuggestion(cwd)
|
|
361
|
+
return upgrade ? `${result}\n${upgrade}` : result
|
|
362
|
+
}
|
|
363
|
+
const cmd = `npx -y tsx ${directory}/src/principles/index.ts --files "${target}" --format console`
|
|
364
|
+
return runCmd(cmd, cwd)
|
|
365
|
+
},
|
|
366
|
+
})
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function gateArchTool(directory: string) {
|
|
370
|
+
return tool({
|
|
371
|
+
description:
|
|
372
|
+
"Run architecture validation (Gate 6 standalone) on the repository with the pinned local @archlinter/cli built-in rules and baseline ratchet.",
|
|
373
|
+
args: {
|
|
374
|
+
config: z
|
|
375
|
+
.string()
|
|
376
|
+
.optional()
|
|
377
|
+
.describe(
|
|
378
|
+
"Optional architecture config file (opts into archlint --config; pre-0.16 schemas are rejected by archlint itself)",
|
|
379
|
+
),
|
|
380
|
+
},
|
|
381
|
+
async execute(args, ctx) {
|
|
382
|
+
const cwd = ctx.directory || directory
|
|
383
|
+
// #447: default to the built-in-rules path — no --config. Forcing
|
|
384
|
+
// `--config architecture.yaml` hit the pre-0.16 schema archlint rejects.
|
|
385
|
+
const archArgs = args.config ? `arch --use-config ${args.config}` : "arch"
|
|
386
|
+
const result = await runXpGate(archArgs, cwd)
|
|
387
|
+
if (result) {
|
|
388
|
+
const upgrade = await getUpgradeSuggestion(cwd)
|
|
389
|
+
return upgrade ? `${result}\n${upgrade}` : result
|
|
390
|
+
}
|
|
391
|
+
const fallback = args.config
|
|
392
|
+
? `npx -y @archlinter/cli scan . --config ${args.config}`
|
|
393
|
+
: `npx -y @archlinter/cli scan .`
|
|
394
|
+
return runCmd(fallback, cwd)
|
|
395
|
+
},
|
|
396
|
+
})
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function readCurrentSessionModel(dbPath: string, sessionId: string): string {
|
|
400
|
+
try {
|
|
401
|
+
const escapedId = sessionId.replace(/'/g, "''")
|
|
402
|
+
const currentRaw = execSync(
|
|
403
|
+
`sqlite3 "${dbPath}" "SELECT model FROM session WHERE id = '${escapedId}'"`,
|
|
404
|
+
{ encoding: "utf8", timeout: SQLITE_TIMEOUT_MS }
|
|
405
|
+
).trim()
|
|
406
|
+
if (!currentRaw) return "unknown"
|
|
407
|
+
try {
|
|
408
|
+
const parsed = JSON.parse(currentRaw)
|
|
409
|
+
return `${parsed.providerID || "?"}/${parsed.id || "?"}`
|
|
410
|
+
} catch {
|
|
411
|
+
return currentRaw
|
|
412
|
+
}
|
|
413
|
+
} catch {
|
|
414
|
+
return "unknown"
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function writeSessionModel(dbPath: string, sessionId: string, modelJson: string): void {
|
|
419
|
+
const escapedId = sessionId.replace(/'/g, "''")
|
|
420
|
+
const escapedModel = modelJson.replace(/'/g, "''")
|
|
421
|
+
const sql = `UPDATE session SET model = '${escapedModel}', time_updated = ${Date.now()} WHERE id = '${escapedId}'`
|
|
422
|
+
try {
|
|
423
|
+
execSync(`sqlite3 "${dbPath}" "${sql}"`, { timeout: SQLITE_TIMEOUT_MS })
|
|
424
|
+
} catch (err) {
|
|
425
|
+
// Contract is to propagate: the caller renders the failure as
|
|
426
|
+
// "Session reload failed: <message>". Wrapped so the IO sits inside
|
|
427
|
+
// error handling (Boy Scout), not to swallow anything.
|
|
428
|
+
throw err
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// Boy Scout (#447): the tool's execute() body inlined 40+ lines at 5 brace
|
|
433
|
+
// levels (tool → execute → try → JSON.stringify options). The logic lives at
|
|
434
|
+
// module scope; execute() is pure wiring.
|
|
435
|
+
function reloadSessionModel(sessionId: string): string {
|
|
436
|
+
const dbPath = join(homedir(), ".local", "share", "opencode", "opencode.db")
|
|
437
|
+
const configDir = join(homedir(), ".config", "opencode")
|
|
438
|
+
const omoConfigPath = join(configDir, "oh-my-openagent.json")
|
|
439
|
+
|
|
440
|
+
if (!existsSync(dbPath)) {
|
|
441
|
+
return "Session reload failed: OpenCode database not found at " + dbPath
|
|
442
|
+
}
|
|
443
|
+
if (!existsSync(omoConfigPath)) {
|
|
444
|
+
return "Session reload failed: oh-my-openagent.json not found at " + omoConfigPath
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
let omoConfig: Record<string, unknown>
|
|
448
|
+
try {
|
|
449
|
+
omoConfig = JSON.parse(readFileSync(omoConfigPath, "utf8"))
|
|
450
|
+
} catch (err) {
|
|
451
|
+
const msg = err instanceof Error ? err.message : String(err)
|
|
452
|
+
return "Session reload failed: could not parse oh-my-openagent.json: " + msg
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
const agents = omoConfig?.["agents"] as Record<string, { model?: string }> | undefined
|
|
456
|
+
const sisyphusModel = agents?.["sisyphus"]?.["model"]
|
|
457
|
+
if (!sisyphusModel || typeof sisyphusModel !== "string") {
|
|
458
|
+
return "Session reload failed: could not find sisyphus.model in oh-my-openagent.json"
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
const slashIdx = sisyphusModel.indexOf("/")
|
|
462
|
+
if (slashIdx === -1) {
|
|
463
|
+
return `Session reload failed: model "${sisyphusModel}" is not in provider/model format`
|
|
464
|
+
}
|
|
465
|
+
const providerID = sisyphusModel.substring(0, slashIdx)
|
|
466
|
+
const modelID = sisyphusModel.substring(slashIdx + 1)
|
|
467
|
+
|
|
468
|
+
const oldModel = readCurrentSessionModel(dbPath, sessionId)
|
|
469
|
+
try {
|
|
470
|
+
writeSessionModel(dbPath, sessionId, JSON.stringify({ id: modelID, providerID }))
|
|
471
|
+
return `Session model reloaded: ${oldModel} → ${providerID}/${modelID}`
|
|
472
|
+
} catch (err) {
|
|
473
|
+
const msg = err instanceof Error ? err.message : String(err)
|
|
474
|
+
return `Session reload failed: ${msg}`
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
function sessionReloadModelTool() {
|
|
479
|
+
return tool({
|
|
480
|
+
description: "Reload session model from config. After switching provider configs (opencode.json + oh-my-openagent.json via switch-coding-plan.sh), updates the session model in OpenCode DB to match the new config. This ensures restarted sessions use the current provider/model.",
|
|
481
|
+
args: {
|
|
482
|
+
sessionId: z.string().optional().describe("Session ID to reload (defaults to current session)"),
|
|
483
|
+
},
|
|
484
|
+
async execute(args, ctx) {
|
|
485
|
+
return reloadSessionModel(args.sessionId || ctx.sessionID)
|
|
486
|
+
},
|
|
487
|
+
})
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// Boy Scout (#447): same treatment as reloadSessionModel — logic at module
|
|
491
|
+
// scope, execute() is pure wiring.
|
|
492
|
+
function renameSession(sessionId: string, newTitle?: string): string {
|
|
493
|
+
const dbPath = join(homedir(), ".local", "share", "opencode", "opencode.db")
|
|
494
|
+
|
|
495
|
+
if (!existsSync(dbPath)) {
|
|
496
|
+
return "Session rename failed: OpenCode database not found at " + dbPath
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// `||` (not `??`): an explicitly empty title falls back to auto-generation,
|
|
500
|
+
// matching the original if/else.
|
|
501
|
+
const title = newTitle || generateSessionTitle(dbPath, sessionId)
|
|
502
|
+
if (!title || title.trim().length === 0) {
|
|
503
|
+
return "Session rename failed: could not generate a title (no user messages found)"
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
const trimmed = title.trim().substring(0, 120)
|
|
507
|
+
const escapedTitle = trimmed.replace(/'/g, "''")
|
|
508
|
+
const escapedId = sessionId.replace(/'/g, "''")
|
|
509
|
+
const sql = `UPDATE session SET title = '${escapedTitle}', time_updated = ${Date.now()} WHERE id = '${escapedId}'`
|
|
510
|
+
try {
|
|
511
|
+
execSync(`sqlite3 "${dbPath}" "${sql}"`, { timeout: SQLITE_TIMEOUT_MS })
|
|
512
|
+
return `Session renamed to: "${trimmed}"`
|
|
513
|
+
} catch (err) {
|
|
514
|
+
const msg = err instanceof Error ? err.message : String(err)
|
|
515
|
+
return `Session rename failed: ${msg}`
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
function sessionRenameTool() {
|
|
520
|
+
return tool({
|
|
521
|
+
description: "Rename an OpenCode session. When called without newTitle, analyzes the session's recent conversation and auto-generates a descriptive title based on the work done.",
|
|
522
|
+
args: {
|
|
523
|
+
sessionId: z.string().optional().describe("Session ID to rename (defaults to current session)"),
|
|
524
|
+
newTitle: z.string().optional().describe("New session title. If omitted, auto-generate from conversation content."),
|
|
525
|
+
},
|
|
526
|
+
async execute(args, ctx) {
|
|
527
|
+
return renameSession(args.sessionId || ctx.sessionID, args.newTitle)
|
|
528
|
+
},
|
|
529
|
+
})
|
|
530
|
+
}
|
|
531
|
+
|
|
322
532
|
export const XpGatePlugin = async (input: OpenCodePluginInput) => {
|
|
323
533
|
const { directory } = input
|
|
324
534
|
|
|
325
535
|
return {
|
|
326
536
|
tool: {
|
|
327
|
-
"gate-check":
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
},
|
|
333
|
-
async execute(args, ctx) {
|
|
334
|
-
const cwd = ctx.directory || directory
|
|
335
|
-
const target = args.path.startsWith("/") ? args.path : `${cwd}/${args.path}`
|
|
336
|
-
const gatesFlag = args.gates?.length ? ` --gates ${args.gates.join(",")}` : ""
|
|
337
|
-
const result = await runXpGate(`check "${target}"${gatesFlag}`, cwd)
|
|
338
|
-
if (result) {
|
|
339
|
-
const upgrade = await getUpgradeSuggestion(cwd)
|
|
340
|
-
return upgrade ? `${result}\n${upgrade}` : result
|
|
341
|
-
}
|
|
342
|
-
const cmd = `node ${directory}/src/npm-package/bin/xp-gate.js check "${target}"${gatesFlag}`
|
|
343
|
-
return runCmd(cmd, cwd)
|
|
344
|
-
},
|
|
345
|
-
}),
|
|
346
|
-
"gate-principles": tool({
|
|
347
|
-
description: "Run Clean Code + SOLID principles checker (Gate 4 standalone) on a file or directory.",
|
|
348
|
-
args: {
|
|
349
|
-
path: z.string().describe("Source file or directory path to check"),
|
|
350
|
-
},
|
|
351
|
-
async execute(args, ctx) {
|
|
352
|
-
const cwd = ctx.directory || directory
|
|
353
|
-
const target = args.path.startsWith("/") ? args.path : `${cwd}/${args.path}`
|
|
354
|
-
const result = await runXpGate(`principles "${target}"`, cwd)
|
|
355
|
-
if (result) {
|
|
356
|
-
const upgrade = await getUpgradeSuggestion(cwd)
|
|
357
|
-
return upgrade ? `${result}\n${upgrade}` : result
|
|
358
|
-
}
|
|
359
|
-
const cmd = `npx -y tsx ${directory}/src/principles/index.ts --files "${target}" --format console`
|
|
360
|
-
return runCmd(cmd, cwd)
|
|
361
|
-
},
|
|
362
|
-
}),
|
|
363
|
-
"gate-arch": tool({
|
|
364
|
-
description: "Run architecture validation (Gate 6 standalone, layer boundary checks) on the repository.",
|
|
365
|
-
args: {
|
|
366
|
-
config: z.string().describe("Path to architecture config file").default("architecture.yaml"),
|
|
367
|
-
},
|
|
368
|
-
async execute(args, ctx) {
|
|
369
|
-
const cwd = ctx.directory || directory
|
|
370
|
-
const config = args.config || "architecture.yaml"
|
|
371
|
-
const result = await runXpGate(`arch --config ${config}`, cwd)
|
|
372
|
-
if (result) {
|
|
373
|
-
const upgrade = await getUpgradeSuggestion(cwd)
|
|
374
|
-
return upgrade ? `${result}\n${upgrade}` : result
|
|
375
|
-
}
|
|
376
|
-
const cmd = `npx -y @archlinter/cli scan . --config ${config}`
|
|
377
|
-
return runCmd(cmd, cwd)
|
|
378
|
-
},
|
|
379
|
-
}),
|
|
380
|
-
"session-reload-model": tool({
|
|
381
|
-
description: "Reload session model from config. After switching provider configs (opencode.json + oh-my-openagent.json via switch-coding-plan.sh), updates the session model in OpenCode DB to match the new config. This ensures restarted sessions use the current provider/model.",
|
|
382
|
-
args: {
|
|
383
|
-
sessionId: z.string().optional().describe("Session ID to reload (defaults to current session)"),
|
|
384
|
-
},
|
|
385
|
-
async execute(args, ctx) {
|
|
386
|
-
const sessionId = args.sessionId || ctx.sessionID
|
|
387
|
-
const dbPath = join(homedir(), ".local", "share", "opencode", "opencode.db")
|
|
388
|
-
const configDir = join(homedir(), ".config", "opencode")
|
|
389
|
-
const omoConfigPath = join(configDir, "oh-my-openagent.json")
|
|
390
|
-
|
|
391
|
-
if (!existsSync(dbPath)) {
|
|
392
|
-
return "Session reload failed: OpenCode database not found at " + dbPath
|
|
393
|
-
}
|
|
394
|
-
if (!existsSync(omoConfigPath)) {
|
|
395
|
-
return "Session reload failed: oh-my-openagent.json not found at " + omoConfigPath
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
let omoConfig: Record<string, unknown>
|
|
399
|
-
try {
|
|
400
|
-
omoConfig = JSON.parse(readFileSync(omoConfigPath, "utf8"))
|
|
401
|
-
} catch (err) {
|
|
402
|
-
const msg = err instanceof Error ? err.message : String(err)
|
|
403
|
-
return "Session reload failed: could not parse oh-my-openagent.json: " + msg
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
const agents = omoConfig?.["agents"] as Record<string, { model?: string }> | undefined
|
|
407
|
-
const sisyphusModel = agents?.["sisyphus"]?.["model"]
|
|
408
|
-
if (!sisyphusModel || typeof sisyphusModel !== "string") {
|
|
409
|
-
return "Session reload failed: could not find sisyphus.model in oh-my-openagent.json"
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
const slashIdx = sisyphusModel.indexOf("/")
|
|
413
|
-
if (slashIdx === -1) {
|
|
414
|
-
return `Session reload failed: model "${sisyphusModel}" is not in provider/model format`
|
|
415
|
-
}
|
|
416
|
-
const providerID = sisyphusModel.substring(0, slashIdx)
|
|
417
|
-
const modelID = sisyphusModel.substring(slashIdx + 1)
|
|
418
|
-
|
|
419
|
-
let oldModel = "unknown"
|
|
420
|
-
try {
|
|
421
|
-
const escapedId = sessionId.replace(/'/g, "''")
|
|
422
|
-
const currentRaw = execSync(
|
|
423
|
-
`sqlite3 "${dbPath}" "SELECT model FROM session WHERE id = '${escapedId}'"`,
|
|
424
|
-
{ encoding: "utf8", timeout: 5000 }
|
|
425
|
-
).trim()
|
|
426
|
-
if (currentRaw) {
|
|
427
|
-
try {
|
|
428
|
-
const parsed = JSON.parse(currentRaw)
|
|
429
|
-
oldModel = `${parsed.providerID || "?"}/${parsed.id || "?"}`
|
|
430
|
-
} catch {
|
|
431
|
-
oldModel = currentRaw
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
} catch {
|
|
435
|
-
oldModel = "unknown"
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
const newModelJson = JSON.stringify({ id: modelID, providerID })
|
|
439
|
-
const escapedId = sessionId.replace(/'/g, "''")
|
|
440
|
-
const escapedModel = newModelJson.replace(/'/g, "''")
|
|
441
|
-
const sql = `UPDATE session SET model = '${escapedModel}', time_updated = ${Date.now()} WHERE id = '${escapedId}'`
|
|
442
|
-
|
|
443
|
-
try {
|
|
444
|
-
execSync(`sqlite3 "${dbPath}" "${sql}"`, { timeout: 5000 })
|
|
445
|
-
return `Session model reloaded: ${oldModel} → ${providerID}/${modelID}`
|
|
446
|
-
} catch (err) {
|
|
447
|
-
const msg = err instanceof Error ? err.message : String(err)
|
|
448
|
-
return `Session reload failed: ${msg}`
|
|
449
|
-
}
|
|
450
|
-
},
|
|
451
|
-
}),
|
|
452
|
-
"session-rename": tool({
|
|
453
|
-
description: "Rename an OpenCode session. When called without newTitle, analyzes the session's recent conversation and auto-generates a descriptive title based on the work done.",
|
|
454
|
-
args: {
|
|
455
|
-
sessionId: z.string().optional().describe("Session ID to rename (defaults to current session)"),
|
|
456
|
-
newTitle: z.string().optional().describe("New session title. If omitted, auto-generates from conversation content."),
|
|
457
|
-
},
|
|
458
|
-
async execute(args, ctx) {
|
|
459
|
-
const sessionId = args.sessionId || ctx.sessionID
|
|
460
|
-
const dbPath = join(homedir(), ".local", "share", "opencode", "opencode.db")
|
|
461
|
-
|
|
462
|
-
if (!existsSync(dbPath)) {
|
|
463
|
-
return "Session rename failed: OpenCode database not found at " + dbPath
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
let title: string
|
|
467
|
-
if (args.newTitle) {
|
|
468
|
-
title = args.newTitle
|
|
469
|
-
} else {
|
|
470
|
-
title = generateSessionTitle(dbPath, sessionId)
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
if (!title || title.trim().length === 0) {
|
|
474
|
-
return "Session rename failed: could not generate a title (no user messages found)"
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
title = title.trim().substring(0, 120)
|
|
478
|
-
|
|
479
|
-
const escapedTitle = title.replace(/'/g, "''")
|
|
480
|
-
const escapedId = sessionId.replace(/'/g, "''")
|
|
481
|
-
const sql = `UPDATE session SET title = '${escapedTitle}', time_updated = ${Date.now()} WHERE id = '${escapedId}'`
|
|
482
|
-
try {
|
|
483
|
-
execSync(`sqlite3 "${dbPath}" "${sql}"`, { timeout: 5000 })
|
|
484
|
-
return `Session renamed to: "${title}"`
|
|
485
|
-
} catch (err) {
|
|
486
|
-
const msg = err instanceof Error ? err.message : String(err)
|
|
487
|
-
return `Session rename failed: ${msg}`
|
|
488
|
-
}
|
|
489
|
-
},
|
|
490
|
-
}),
|
|
537
|
+
"gate-check": gateCheckTool(directory),
|
|
538
|
+
"gate-principles": gatePrinciplesTool(directory),
|
|
539
|
+
"gate-arch": gateArchTool(directory),
|
|
540
|
+
"session-reload-model": sessionReloadModelTool(),
|
|
541
|
+
"session-rename": sessionRenameTool(),
|
|
491
542
|
},
|
|
492
543
|
"chat.message": async (_input: { message: string }) => {
|
|
493
544
|
if (!checked) {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
|
|
2
2
|
|
|
3
|
-
**Generated:** 2026-10-
|
|
4
|
-
**Commit:**
|
|
5
|
-
**Branch:**
|
|
6
|
-
**Version:** 0.
|
|
3
|
+
**Generated:** 2026-10-13
|
|
4
|
+
**Commit:** 05a08fcf
|
|
5
|
+
**Branch:** main
|
|
6
|
+
**Version:** 0.21.0.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
Delphi Consensus Review — multi-round anonymous expert review (≥90% threshold, exactly 3 experts with distinct executable model IDs). Supports design, requirements, and code-walkthrough modes.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/SPRINT-FLOW KNOWLEDGE BASE
|
|
2
2
|
|
|
3
|
-
**Generated:** 2026-10-
|
|
4
|
-
**Commit:**
|
|
5
|
-
**Branch:**
|
|
6
|
-
**Version:** 0.
|
|
3
|
+
**Generated:** 2026-10-13
|
|
4
|
+
**Commit:** 05a08fcf
|
|
5
|
+
**Branch:** main
|
|
6
|
+
**Version:** 0.21.0.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.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/TEST-SPECIFICATION-ALIGNMENT KNOWLEDGE BASE
|
|
2
2
|
|
|
3
|
-
**Generated:** 2026-10-
|
|
4
|
-
**Commit:**
|
|
5
|
-
**Branch:**
|
|
6
|
-
**Version:** 0.
|
|
3
|
+
**Generated:** 2026-10-13
|
|
4
|
+
**Commit:** 05a08fcf
|
|
5
|
+
**Branch:** main
|
|
6
|
+
**Version:** 0.21.0.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "xp-gate",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"displayName": "XP-Gate",
|
|
5
5
|
"description": "Extreme Programming quality gates + AI workflow skills for Qoder. Includes 12 pre-commit gates (Gate 0-11), 8 pre-push gates, Sprint Flow (6 phases), and Delphi multi-expert review (>=90% consensus).",
|
|
6
6
|
"author": {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
|
|
2
2
|
|
|
3
|
-
**Generated:** 2026-10-
|
|
4
|
-
**Commit:**
|
|
5
|
-
**Branch:**
|
|
6
|
-
**Version:** 0.
|
|
3
|
+
**Generated:** 2026-10-13
|
|
4
|
+
**Commit:** 05a08fcf
|
|
5
|
+
**Branch:** main
|
|
6
|
+
**Version:** 0.21.0.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
Delphi Consensus Review — multi-round anonymous expert review (≥90% threshold, exactly 3 experts with distinct executable model IDs). Supports design, requirements, and code-walkthrough modes.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/SPRINT-FLOW KNOWLEDGE BASE
|
|
2
2
|
|
|
3
|
-
**Generated:** 2026-10-
|
|
4
|
-
**Commit:**
|
|
5
|
-
**Branch:**
|
|
6
|
-
**Version:** 0.
|
|
3
|
+
**Generated:** 2026-10-13
|
|
4
|
+
**Commit:** 05a08fcf
|
|
5
|
+
**Branch:** main
|
|
6
|
+
**Version:** 0.21.0.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.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/TEST-SPECIFICATION-ALIGNMENT KNOWLEDGE BASE
|
|
2
2
|
|
|
3
|
-
**Generated:** 2026-10-
|
|
4
|
-
**Commit:**
|
|
5
|
-
**Branch:**
|
|
6
|
-
**Version:** 0.
|
|
3
|
+
**Generated:** 2026-10-13
|
|
4
|
+
**Commit:** 05a08fcf
|
|
5
|
+
**Branch:** main
|
|
6
|
+
**Version:** 0.21.0.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.
|
package/principles/AGENTS.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# PRINCIPLES CHECKER MODULE
|
|
2
2
|
|
|
3
|
-
**Generated:** 2026-10-
|
|
4
|
-
**Commit:**
|
|
5
|
-
**Branch:**
|
|
6
|
-
**Version:** 0.
|
|
3
|
+
**Generated:** 2026-10-13
|
|
4
|
+
**Commit:** 05a08fcf
|
|
5
|
+
**Branch:** main
|
|
6
|
+
**Version:** 0.21.0.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
Clean Code & SOLID principles checker — **Gate 4** of pre-commit. 14 rules × 9 language adapters, SARIF 2.1.0 output. Houses the **Boy Scout Rule** enforcement engine (Gate 6) and warning-baseline storage.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
|
|
2
2
|
|
|
3
|
-
**Generated:** 2026-10-
|
|
4
|
-
**Commit:**
|
|
5
|
-
**Branch:**
|
|
6
|
-
**Version:** 0.
|
|
3
|
+
**Generated:** 2026-10-13
|
|
4
|
+
**Commit:** 05a08fcf
|
|
5
|
+
**Branch:** main
|
|
6
|
+
**Version:** 0.21.0.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
Delphi Consensus Review — multi-round anonymous expert review (≥90% threshold, exactly 3 experts with distinct executable model IDs). Supports design, requirements, and code-walkthrough modes.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/SPRINT-FLOW KNOWLEDGE BASE
|
|
2
2
|
|
|
3
|
-
**Generated:** 2026-10-
|
|
4
|
-
**Commit:**
|
|
5
|
-
**Branch:**
|
|
6
|
-
**Version:** 0.
|
|
3
|
+
**Generated:** 2026-10-13
|
|
4
|
+
**Commit:** 05a08fcf
|
|
5
|
+
**Branch:** main
|
|
6
|
+
**Version:** 0.21.0.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.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/TEST-SPECIFICATION-ALIGNMENT KNOWLEDGE BASE
|
|
2
2
|
|
|
3
|
-
**Generated:** 2026-10-
|
|
4
|
-
**Commit:**
|
|
5
|
-
**Branch:**
|
|
6
|
-
**Version:** 0.
|
|
3
|
+
**Generated:** 2026-10-13
|
|
4
|
+
**Commit:** 05a08fcf
|
|
5
|
+
**Branch:** main
|
|
6
|
+
**Version:** 0.21.0.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.
|