opencode-gitlab-dap 1.8.3 → 1.9.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/dist/index.js CHANGED
@@ -2090,7 +2090,30 @@ This project may have persistent memory and skills available via knowledge tools
2090
2090
  Use gitlab_memory_load to check for existing project context (facts, decisions, patterns).
2091
2091
  Use gitlab_skill_list to discover available task-specific skills.
2092
2092
  When you learn something new about the project, use gitlab_memory_record to preserve it.
2093
- When you complete a significant task, consider using gitlab_memory_log_session to log learnings.`;
2093
+ When you complete a significant task, consider using gitlab_memory_log_session to log learnings.
2094
+
2095
+ ### Bootstrap Project Memory
2096
+ When the user says "bootstrap project memory", "initialize memory", or "build project knowledge":
2097
+ 1. FIRST: Determine the project path by running \`run_git_command("remote", ["-v"])\` and extracting the path from the git remote URL (e.g., "git@gitlab.com:my-group/my-project.git" \u2192 "my-group/my-project"). NEVER guess the project path.
2098
+ 2. Call gitlab_memory_load with the extracted project path to check if memory already exists.
2099
+ 3. If memory exists, show a summary and ask if the user wants to refresh it.
2100
+ 4. If memory is empty (or user wants refresh), gather project knowledge by:
2101
+ a. Read the project README, key config files, and codebase structure (use file/repo tools).
2102
+ b. Fetch project details via gitlab_get_project.
2103
+ c. List open issues (gitlab_list_issues, state=opened, limit=20).
2104
+ d. List open merge requests (gitlab_list_merge_requests, state=opened, limit=10).
2105
+ e. Check recent pipelines (gitlab_list_pipelines, limit=5).
2106
+ f. List project members (gitlab_list_project_members).
2107
+ 5. Record each category as separate memory entries using gitlab_memory_record:
2108
+ - fact: project overview (name, purpose, tech stack, language, dependencies)
2109
+ - fact: architecture and codebase structure (key files, modules, patterns)
2110
+ - fact: CI/CD pipeline configuration (stages, jobs, deployment targets)
2111
+ - fact: open issues summary (count, key issues, labels)
2112
+ - fact: open MRs summary (count, key MRs, review status)
2113
+ - fact: team and contributors
2114
+ - pattern: development workflow observations (branching, review process, release cycle)
2115
+ 6. Log a session with gitlab_memory_log_session summarizing the bootstrap.
2116
+ IMPORTANT: Always extract the project path from git remote \u2014 never guess it. Fire multiple gitlab_memory_record calls in parallel \u2014 each creates its own page, so parallel writes are safe.`;
2094
2117
 
2095
2118
  // src/hooks.ts
2096
2119
  function buildFlowSubagentPrompt(flow, projectPath, projectUrl) {
@@ -3443,13 +3466,13 @@ async function searchWikiPages(instanceUrl, token, scope, id, query) {
3443
3466
  // src/tools/memory-tools.ts
3444
3467
  var z5 = tool5.schema;
3445
3468
  var PREFIX = "agents";
3446
- var MEMORY_SLUGS = {
3469
+ var MEMORY_DIRS = {
3447
3470
  all: [`${PREFIX}/memory/facts`, `${PREFIX}/memory/decisions`, `${PREFIX}/memory/patterns`],
3448
3471
  facts: [`${PREFIX}/memory/facts`],
3449
3472
  decisions: [`${PREFIX}/memory/decisions`],
3450
3473
  patterns: [`${PREFIX}/memory/patterns`]
3451
3474
  };
3452
- var RECORD_SLUG = {
3475
+ var RECORD_DIR = {
3453
3476
  fact: `${PREFIX}/memory/facts`,
3454
3477
  decision: `${PREFIX}/memory/decisions`,
3455
3478
  pattern: `${PREFIX}/memory/patterns`
@@ -3460,52 +3483,9 @@ function today() {
3460
3483
  function slugify(text) {
3461
3484
  return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "").slice(0, 60);
3462
3485
  }
3463
- async function safeRead(instanceUrl, token, scope, id, slug) {
3464
- try {
3465
- const page = await getWikiPage(instanceUrl, token, scope, id, slug);
3466
- return page.content;
3467
- } catch {
3468
- return null;
3469
- }
3470
- }
3471
- var MAX_RETRIES = 3;
3472
- var RETRY_DELAY_MS = 1e3;
3473
- async function sleep2(ms) {
3474
- return new Promise((resolve) => setTimeout(resolve, ms));
3475
- }
3476
- async function appendToPage(instanceUrl, token, scope, id, slug, newContent) {
3477
- for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
3478
- const existing2 = await safeRead(instanceUrl, token, scope, id, slug);
3479
- if (existing2 !== null) {
3480
- try {
3481
- await updateWikiPage(instanceUrl, token, scope, id, slug, existing2 + "\n\n" + newContent);
3482
- return;
3483
- } catch (err) {
3484
- if (attempt < MAX_RETRIES - 1 && err.message?.includes("reference update")) {
3485
- await sleep2(RETRY_DELAY_MS * (attempt + 1));
3486
- continue;
3487
- }
3488
- throw err;
3489
- }
3490
- } else {
3491
- try {
3492
- await createWikiPage(instanceUrl, token, scope, id, slug, newContent);
3493
- return;
3494
- } catch (err) {
3495
- if (err.message?.includes("Duplicate page") || err.message?.includes("reference update")) {
3496
- await sleep2(RETRY_DELAY_MS * (attempt + 1));
3497
- continue;
3498
- }
3499
- throw err;
3500
- }
3501
- }
3502
- }
3503
- const existing = await safeRead(instanceUrl, token, scope, id, slug);
3504
- if (existing !== null) {
3505
- await updateWikiPage(instanceUrl, token, scope, id, slug, existing + "\n\n" + newContent);
3506
- } else {
3507
- throw new Error(`Failed to append to ${slug} after ${MAX_RETRIES} retries`);
3508
- }
3486
+ function titleFromContent(content) {
3487
+ const first = content.split("\n")[0].replace(/^[#*\->\s]+/, "").trim();
3488
+ return first.slice(0, 80) || "untitled";
3509
3489
  }
3510
3490
  function validateProjectId(projectId) {
3511
3491
  if (!projectId.includes("/")) {
@@ -3542,25 +3522,38 @@ function makeMemoryTools(ctx) {
3542
3522
  const auth = authAndValidate(args.project_id);
3543
3523
  const { scope, id } = resolveScope(args);
3544
3524
  const memType = args.type ?? "all";
3545
- const slugs = MEMORY_SLUGS[memType];
3546
- if (!slugs) return `Unknown memory type: ${memType}`;
3547
- const sections = [];
3548
- for (const slug of slugs) {
3549
- const content = await safeRead(auth.instanceUrl, auth.token, scope, id, slug);
3550
- if (content) {
3551
- const label = slug.split("/").pop();
3552
- sections.push(`## ${label.charAt(0).toUpperCase() + label.slice(1)}
3553
-
3554
- ${content}`);
3525
+ const dirs = MEMORY_DIRS[memType];
3526
+ if (!dirs) return `Unknown memory type: ${memType}`;
3527
+ try {
3528
+ const allPages = await listWikiPages(
3529
+ auth.instanceUrl,
3530
+ auth.token,
3531
+ scope,
3532
+ id,
3533
+ true
3534
+ );
3535
+ const sections = [];
3536
+ for (const dir of dirs) {
3537
+ const label = dir.split("/").pop();
3538
+ const pages = allPages.filter((p) => p.slug.startsWith(dir + "/") && p.content);
3539
+ if (pages.length === 0) continue;
3540
+ const entries = pages.map((p) => p.content).join("\n\n---\n\n");
3541
+ sections.push(
3542
+ `## ${label.charAt(0).toUpperCase() + label.slice(1)} (${pages.length})
3543
+
3544
+ ${entries}`
3545
+ );
3555
3546
  }
3547
+ if (sections.length === 0)
3548
+ return "No project memory found. Use gitlab_memory_record to start building project knowledge.";
3549
+ return sections.join("\n\n---\n\n");
3550
+ } catch (err) {
3551
+ return `Error loading memory: ${err.message}`;
3556
3552
  }
3557
- if (sections.length === 0)
3558
- return "No project memory found. Use gitlab_memory_record to start building project knowledge.";
3559
- return sections.join("\n\n---\n\n");
3560
3553
  }
3561
3554
  }),
3562
3555
  gitlab_memory_record: tool5({
3563
- description: "Record a fact, decision, or pattern in project memory.\nFacts: stable truths about the project (e.g., deploy targets, tech stack, team conventions).\nDecisions: architectural choices with reasoning (why X was chosen over Y).\nPatterns: recurring observations that may evolve into skills over time.\nEntries are automatically timestamped and appended to the appropriate memory page.\nMultiple facts can be recorded in a single call by separating them with newlines.",
3556
+ description: "Record a fact, decision, or pattern in project memory.\nFacts: stable truths about the project (e.g., deploy targets, tech stack, team conventions).\nDecisions: architectural choices with reasoning (why X was chosen over Y).\nPatterns: recurring observations that may evolve into skills over time.\nEach record creates its own page \u2014 safe for parallel writes.",
3564
3557
  args: {
3565
3558
  project_id: z5.string().describe(
3566
3559
  'FULL project path with namespace (e.g., "gitlab-org/gitlab"). Must contain a slash. Never use just the project name.'
@@ -3573,24 +3566,18 @@ ${content}`);
3573
3566
  execute: async (args) => {
3574
3567
  const auth = authAndValidate(args.project_id);
3575
3568
  const { scope, id } = resolveScope(args);
3576
- const slug = RECORD_SLUG[args.type];
3577
- if (!slug) return `Unknown memory type: ${args.type}`;
3569
+ const dir = RECORD_DIR[args.type];
3570
+ if (!dir) return `Unknown memory type: ${args.type}`;
3578
3571
  const date = today();
3579
- let formatted;
3580
- if (args.type === "decision") {
3581
- const firstLine = args.content.split("\n")[0];
3582
- const rest = args.content.split("\n").slice(1).join("\n").trim();
3583
- formatted = `## ${date}: ${firstLine}${rest ? "\n" + rest : ""}`;
3584
- } else if (args.type === "fact") {
3585
- formatted = `## ${date}
3586
- - ${args.content.replace(/\n/g, "\n- ")}`;
3587
- } else {
3588
- formatted = `## ${date}
3589
- ${args.content}`;
3590
- }
3572
+ const title = titleFromContent(args.content);
3573
+ const slug = `${dir}/${date}-${slugify(title)}`;
3574
+ const header = `*Recorded: ${date} | Type: ${args.type}*
3575
+
3576
+ `;
3577
+ const body = header + args.content;
3591
3578
  try {
3592
- await appendToPage(auth.instanceUrl, auth.token, scope, id, slug, formatted);
3593
- return `Recorded ${args.type} in project memory.`;
3579
+ await createWikiPage(auth.instanceUrl, auth.token, scope, id, slug, body);
3580
+ return `Recorded ${args.type} in project memory: ${slug}`;
3594
3581
  } catch (err) {
3595
3582
  return `Error recording ${args.type}: ${err.message}`;
3596
3583
  }
@@ -3646,32 +3633,12 @@ ${args.content}`;
3646
3633
  const { scope, id } = resolveScope(args);
3647
3634
  const date = today();
3648
3635
  const slug = `${PREFIX}/memory/sessions/${date}-${slugify(args.title)}`;
3649
- for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
3650
- try {
3651
- await createWikiPage(auth.instanceUrl, auth.token, scope, id, slug, args.summary);
3652
- return `Session logged: ${slug}`;
3653
- } catch (err) {
3654
- const msg = err.message ?? "";
3655
- if (msg.includes("Duplicate page") || msg.includes("already exists") || msg.includes("422")) {
3656
- try {
3657
- await updateWikiPage(auth.instanceUrl, auth.token, scope, id, slug, args.summary);
3658
- return `Session log updated: ${slug}`;
3659
- } catch (err2) {
3660
- if (attempt < MAX_RETRIES - 1 && err2.message?.includes("reference update")) {
3661
- await sleep2(RETRY_DELAY_MS * (attempt + 1));
3662
- continue;
3663
- }
3664
- return `Error logging session: ${err2.message}`;
3665
- }
3666
- }
3667
- if (attempt < MAX_RETRIES - 1 && msg.includes("reference update")) {
3668
- await sleep2(RETRY_DELAY_MS * (attempt + 1));
3669
- continue;
3670
- }
3671
- return `Error logging session: ${msg}`;
3672
- }
3636
+ try {
3637
+ await createWikiPage(auth.instanceUrl, auth.token, scope, id, slug, args.summary);
3638
+ return `Session logged: ${slug}`;
3639
+ } catch (err) {
3640
+ return `Error logging session: ${err.message}`;
3673
3641
  }
3674
- return `Error logging session: failed after ${MAX_RETRIES} retries`;
3675
3642
  }
3676
3643
  })
3677
3644
  };