opencode-gitlab-dap 1.8.1 → 1.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -3637,14 +3637,51 @@ async function safeRead(instanceUrl, token, scope, id, slug) {
3637
3637
  return null;
3638
3638
  }
3639
3639
  }
3640
+ var MAX_RETRIES = 3;
3641
+ var RETRY_DELAY_MS = 1e3;
3642
+ async function sleep2(ms) {
3643
+ return new Promise((resolve) => setTimeout(resolve, ms));
3644
+ }
3640
3645
  async function appendToPage(instanceUrl, token, scope, id, slug, newContent) {
3646
+ for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
3647
+ const existing2 = await safeRead(instanceUrl, token, scope, id, slug);
3648
+ if (existing2 !== null) {
3649
+ try {
3650
+ await updateWikiPage(instanceUrl, token, scope, id, slug, existing2 + "\n\n" + newContent);
3651
+ return;
3652
+ } catch (err) {
3653
+ if (attempt < MAX_RETRIES - 1 && err.message?.includes("reference update")) {
3654
+ await sleep2(RETRY_DELAY_MS * (attempt + 1));
3655
+ continue;
3656
+ }
3657
+ throw err;
3658
+ }
3659
+ } else {
3660
+ try {
3661
+ await createWikiPage(instanceUrl, token, scope, id, slug, newContent);
3662
+ return;
3663
+ } catch (err) {
3664
+ if (err.message?.includes("Duplicate page") || err.message?.includes("reference update")) {
3665
+ await sleep2(RETRY_DELAY_MS * (attempt + 1));
3666
+ continue;
3667
+ }
3668
+ throw err;
3669
+ }
3670
+ }
3671
+ }
3641
3672
  const existing = await safeRead(instanceUrl, token, scope, id, slug);
3642
3673
  if (existing !== null) {
3643
3674
  await updateWikiPage(instanceUrl, token, scope, id, slug, existing + "\n\n" + newContent);
3644
3675
  } else {
3645
- await createWikiPage(instanceUrl, token, scope, id, slug, newContent);
3676
+ throw new Error(`Failed to append to ${slug} after ${MAX_RETRIES} retries`);
3646
3677
  }
3647
3678
  }
3679
+ function validateProjectId(projectId) {
3680
+ if (!projectId.includes("/")) {
3681
+ return `Invalid project_id "${projectId}". Must be the full project path containing at least one slash (e.g., "my-group/my-project"), not just the project name.`;
3682
+ }
3683
+ return null;
3684
+ }
3648
3685
  function resolveScope(args) {
3649
3686
  if (args.scope === "groups" && args.group_id) {
3650
3687
  return { scope: "groups", id: args.group_id };
@@ -3652,18 +3689,26 @@ function resolveScope(args) {
3652
3689
  return { scope: "projects", id: args.project_id };
3653
3690
  }
3654
3691
  function makeMemoryTools(ctx) {
3692
+ function authAndValidate(projectId) {
3693
+ const auth = ctx.ensureAuth();
3694
+ if (!auth) throw new Error("GitLab authentication not available");
3695
+ const err = validateProjectId(projectId);
3696
+ if (err) throw new Error(err);
3697
+ return auth;
3698
+ }
3655
3699
  return {
3656
3700
  gitlab_memory_load: (0, import_plugin5.tool)({
3657
3701
  description: "Load project memory to understand context, known facts, past decisions, and observed patterns.\nUse this at the start of complex tasks to check what is already known about the project.\nReturns accumulated knowledge from previous sessions.",
3658
3702
  args: {
3659
- project_id: z5.string().describe('Project path (e.g., "gitlab-org/gitlab"). Use the same value consistently.'),
3703
+ project_id: z5.string().describe(
3704
+ 'FULL project path with namespace (e.g., "gitlab-org/gitlab"). Must contain a slash. Never use just the project name.'
3705
+ ),
3660
3706
  type: z5.enum(["all", "facts", "decisions", "patterns"]).optional().describe('Which memory to load: "all" (default), "facts", "decisions", or "patterns"'),
3661
3707
  scope: z5.enum(["projects", "groups"]).optional().describe("Scope (default: projects)"),
3662
3708
  group_id: z5.string().optional().describe("Group path (required when scope is groups)")
3663
3709
  },
3664
3710
  execute: async (args) => {
3665
- const auth = ctx.ensureAuth();
3666
- if (!auth) throw new Error("GitLab authentication not available");
3711
+ const auth = authAndValidate(args.project_id);
3667
3712
  const { scope, id } = resolveScope(args);
3668
3713
  const memType = args.type ?? "all";
3669
3714
  const slugs = MEMORY_SLUGS[memType];
@@ -3686,15 +3731,16 @@ ${content}`);
3686
3731
  gitlab_memory_record: (0, import_plugin5.tool)({
3687
3732
  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.",
3688
3733
  args: {
3689
- project_id: z5.string().describe('Project path (e.g., "gitlab-org/gitlab"). Use the same value consistently.'),
3734
+ project_id: z5.string().describe(
3735
+ 'FULL project path with namespace (e.g., "gitlab-org/gitlab"). Must contain a slash. Never use just the project name.'
3736
+ ),
3690
3737
  type: z5.enum(["fact", "decision", "pattern"]).describe("Type of knowledge to record"),
3691
3738
  content: z5.string().describe("The knowledge to record (markdown)"),
3692
3739
  scope: z5.enum(["projects", "groups"]).optional().describe("Scope (default: projects)"),
3693
3740
  group_id: z5.string().optional().describe("Group path (required when scope is groups)")
3694
3741
  },
3695
3742
  execute: async (args) => {
3696
- const auth = ctx.ensureAuth();
3697
- if (!auth) throw new Error("GitLab authentication not available");
3743
+ const auth = authAndValidate(args.project_id);
3698
3744
  const { scope, id } = resolveScope(args);
3699
3745
  const slug = RECORD_SLUG[args.type];
3700
3746
  if (!slug) return `Unknown memory type: ${args.type}`;
@@ -3722,14 +3768,15 @@ ${args.content}`;
3722
3768
  gitlab_memory_recall: (0, import_plugin5.tool)({
3723
3769
  description: "Search project knowledge for relevant information.\nSearches across all memory pages, session logs, and skills.\nUse this to check if something is already known before investigating.",
3724
3770
  args: {
3725
- project_id: z5.string().describe('Project path (e.g., "gitlab-org/gitlab"). Use the same value consistently.'),
3771
+ project_id: z5.string().describe(
3772
+ 'FULL project path with namespace (e.g., "gitlab-org/gitlab"). Must contain a slash. Never use just the project name.'
3773
+ ),
3726
3774
  query: z5.string().describe("What to search for"),
3727
3775
  scope: z5.enum(["projects", "groups"]).optional().describe("Scope (default: projects)"),
3728
3776
  group_id: z5.string().optional().describe("Group path (required when scope is groups)")
3729
3777
  },
3730
3778
  execute: async (args) => {
3731
- const auth = ctx.ensureAuth();
3732
- if (!auth) throw new Error("GitLab authentication not available");
3779
+ const auth = authAndValidate(args.project_id);
3733
3780
  const { scope, id } = resolveScope(args);
3734
3781
  try {
3735
3782
  const results = await searchWikiPages(
@@ -3753,7 +3800,9 @@ ${args.content}`;
3753
3800
  gitlab_memory_log_session: (0, import_plugin5.tool)({
3754
3801
  description: "Log a session summary including what was accomplished, what was learned, and any suggestions.\nUse this at the end of significant work sessions to preserve context for future sessions.",
3755
3802
  args: {
3756
- project_id: z5.string().describe('Project path (e.g., "gitlab-org/gitlab"). Use the same value consistently.'),
3803
+ project_id: z5.string().describe(
3804
+ 'FULL project path with namespace (e.g., "gitlab-org/gitlab"). Must contain a slash. Never use just the project name.'
3805
+ ),
3757
3806
  title: z5.string().describe('Brief session title (e.g., "fix-ai-gateway-healthcheck")'),
3758
3807
  summary: z5.string().describe(
3759
3808
  "Session summary in markdown (what happened, what was learned, what went wrong)"
@@ -3762,25 +3811,36 @@ ${args.content}`;
3762
3811
  group_id: z5.string().optional().describe("Group path (required when scope is groups)")
3763
3812
  },
3764
3813
  execute: async (args) => {
3765
- const auth = ctx.ensureAuth();
3766
- if (!auth) throw new Error("GitLab authentication not available");
3814
+ const auth = authAndValidate(args.project_id);
3767
3815
  const { scope, id } = resolveScope(args);
3768
3816
  const date = today();
3769
3817
  const slug = `${PREFIX}/memory/sessions/${date}-${slugify(args.title)}`;
3770
- try {
3771
- await createWikiPage(auth.instanceUrl, auth.token, scope, id, slug, args.summary);
3772
- return `Session logged: ${slug}`;
3773
- } catch (err) {
3774
- if (err.message?.includes("already exists") || err.message?.includes("422")) {
3775
- try {
3776
- await updateWikiPage(auth.instanceUrl, auth.token, scope, id, slug, args.summary);
3777
- return `Session log updated: ${slug}`;
3778
- } catch (err2) {
3779
- return `Error logging session: ${err2.message}`;
3818
+ for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
3819
+ try {
3820
+ await createWikiPage(auth.instanceUrl, auth.token, scope, id, slug, args.summary);
3821
+ return `Session logged: ${slug}`;
3822
+ } catch (err) {
3823
+ const msg = err.message ?? "";
3824
+ if (msg.includes("Duplicate page") || msg.includes("already exists") || msg.includes("422")) {
3825
+ try {
3826
+ await updateWikiPage(auth.instanceUrl, auth.token, scope, id, slug, args.summary);
3827
+ return `Session log updated: ${slug}`;
3828
+ } catch (err2) {
3829
+ if (attempt < MAX_RETRIES - 1 && err2.message?.includes("reference update")) {
3830
+ await sleep2(RETRY_DELAY_MS * (attempt + 1));
3831
+ continue;
3832
+ }
3833
+ return `Error logging session: ${err2.message}`;
3834
+ }
3835
+ }
3836
+ if (attempt < MAX_RETRIES - 1 && msg.includes("reference update")) {
3837
+ await sleep2(RETRY_DELAY_MS * (attempt + 1));
3838
+ continue;
3780
3839
  }
3840
+ return `Error logging session: ${msg}`;
3781
3841
  }
3782
- return `Error logging session: ${err.message}`;
3783
3842
  }
3843
+ return `Error logging session: failed after ${MAX_RETRIES} retries`;
3784
3844
  }
3785
3845
  })
3786
3846
  };
@@ -3798,19 +3858,33 @@ function resolveScope2(args) {
3798
3858
  }
3799
3859
  return { scope: "projects", id: args.project_id };
3800
3860
  }
3861
+ function validateProjectId2(projectId) {
3862
+ if (!projectId.includes("/")) {
3863
+ return `Invalid project_id "${projectId}". Must be the full project path containing at least one slash (e.g., "my-group/my-project"), not just the project name.`;
3864
+ }
3865
+ return null;
3866
+ }
3801
3867
  function makeSkillTools(ctx) {
3868
+ function authAndValidate(projectId) {
3869
+ const auth = ctx.ensureAuth();
3870
+ if (!auth) throw new Error("GitLab authentication not available");
3871
+ const err = validateProjectId2(projectId);
3872
+ if (err) throw new Error(err);
3873
+ return auth;
3874
+ }
3802
3875
  return {
3803
3876
  gitlab_skill_list: (0, import_plugin6.tool)({
3804
3877
  description: "List available project skills and optionally draft skills.\nSkills define step-by-step procedures for common tasks (e.g., incident retros, debugging, deployments).",
3805
3878
  args: {
3806
- project_id: z6.string().describe('Project path (e.g., "gitlab-org/gitlab"). Use the same value consistently.'),
3879
+ project_id: z6.string().describe(
3880
+ 'FULL project path with namespace (e.g., "gitlab-org/gitlab"). Must contain a slash. Never use just the project name.'
3881
+ ),
3807
3882
  include_drafts: z6.boolean().optional().describe("Also list draft skills (default: false)"),
3808
3883
  scope: z6.enum(["projects", "groups"]).optional().describe("Scope (default: projects)"),
3809
3884
  group_id: z6.string().optional().describe("Group path (required when scope is groups)")
3810
3885
  },
3811
3886
  execute: async (args) => {
3812
- const auth = ctx.ensureAuth();
3813
- if (!auth) throw new Error("GitLab authentication not available");
3887
+ const auth = authAndValidate(args.project_id);
3814
3888
  const { scope, id } = resolveScope2(args);
3815
3889
  try {
3816
3890
  const pages = await listWikiPages(auth.instanceUrl, auth.token, scope, id);
@@ -3840,14 +3914,15 @@ function makeSkillTools(ctx) {
3840
3914
  gitlab_skill_load: (0, import_plugin6.tool)({
3841
3915
  description: "Load a specific skill by name.\nSkills contain step-by-step instructions for common tasks.\nChecks published skills first, then falls back to draft skills.",
3842
3916
  args: {
3843
- project_id: z6.string().describe('Project path (e.g., "gitlab-org/gitlab"). Use the same value consistently.'),
3917
+ project_id: z6.string().describe(
3918
+ 'FULL project path with namespace (e.g., "gitlab-org/gitlab"). Must contain a slash. Never use just the project name.'
3919
+ ),
3844
3920
  name: z6.string().describe('Skill name (e.g., "incident-retro", "helm-rollback")'),
3845
3921
  scope: z6.enum(["projects", "groups"]).optional().describe("Scope (default: projects)"),
3846
3922
  group_id: z6.string().optional().describe("Group path (required when scope is groups)")
3847
3923
  },
3848
3924
  execute: async (args) => {
3849
- const auth = ctx.ensureAuth();
3850
- if (!auth) throw new Error("GitLab authentication not available");
3925
+ const auth = authAndValidate(args.project_id);
3851
3926
  const { scope, id } = resolveScope2(args);
3852
3927
  try {
3853
3928
  const page = await getWikiPage(
@@ -3879,7 +3954,9 @@ ${draft.content}`;
3879
3954
  gitlab_skill_save: (0, import_plugin6.tool)({
3880
3955
  description: "Create or update a skill.\nSkills define step-by-step procedures for common tasks.\nUse draft=true for skills that haven't been proven yet.",
3881
3956
  args: {
3882
- project_id: z6.string().describe('Project path (e.g., "gitlab-org/gitlab"). Use the same value consistently.'),
3957
+ project_id: z6.string().describe(
3958
+ 'FULL project path with namespace (e.g., "gitlab-org/gitlab"). Must contain a slash. Never use just the project name.'
3959
+ ),
3883
3960
  name: z6.string().describe('Skill name (e.g., "incident-retro")'),
3884
3961
  content: z6.string().describe("Skill content in markdown"),
3885
3962
  draft: z6.boolean().optional().describe("Save as draft skill (default: false)"),
@@ -3887,35 +3964,44 @@ ${draft.content}`;
3887
3964
  group_id: z6.string().optional().describe("Group path (required when scope is groups)")
3888
3965
  },
3889
3966
  execute: async (args) => {
3890
- const auth = ctx.ensureAuth();
3891
- if (!auth) throw new Error("GitLab authentication not available");
3967
+ const auth = authAndValidate(args.project_id);
3892
3968
  const { scope, id } = resolveScope2(args);
3893
3969
  const prefix = args.draft ? DRAFTS_PREFIX : SKILLS_PREFIX;
3894
3970
  const slug = `${prefix}/${args.name}`;
3895
- try {
3896
- await updateWikiPage(auth.instanceUrl, auth.token, scope, id, slug, args.content);
3897
- return `Updated ${args.draft ? "draft " : ""}skill: ${args.name}`;
3898
- } catch {
3971
+ const label = args.draft ? "draft " : "";
3972
+ for (let attempt = 0; attempt < 3; attempt++) {
3899
3973
  try {
3900
- await createWikiPage(auth.instanceUrl, auth.token, scope, id, slug, args.content);
3901
- return `Created ${args.draft ? "draft " : ""}skill: ${args.name}`;
3902
- } catch (err) {
3903
- return `Error saving skill: ${err.message}`;
3974
+ await updateWikiPage(auth.instanceUrl, auth.token, scope, id, slug, args.content);
3975
+ return `Updated ${label}skill: ${args.name}`;
3976
+ } catch {
3977
+ try {
3978
+ await createWikiPage(auth.instanceUrl, auth.token, scope, id, slug, args.content);
3979
+ return `Created ${label}skill: ${args.name}`;
3980
+ } catch (err) {
3981
+ const msg = err.message ?? "";
3982
+ if (msg.includes("Duplicate page") || msg.includes("reference update")) {
3983
+ await new Promise((r) => setTimeout(r, 1e3 * (attempt + 1)));
3984
+ continue;
3985
+ }
3986
+ return `Error saving skill: ${msg}`;
3987
+ }
3904
3988
  }
3905
3989
  }
3990
+ return `Error saving skill: failed after 3 retries`;
3906
3991
  }
3907
3992
  }),
3908
3993
  gitlab_skill_promote: (0, import_plugin6.tool)({
3909
3994
  description: "Promote a draft skill to published.\nMoves the skill from the drafts directory to the published skills directory.",
3910
3995
  args: {
3911
- project_id: z6.string().describe('Project path (e.g., "gitlab-org/gitlab"). Use the same value consistently.'),
3996
+ project_id: z6.string().describe(
3997
+ 'FULL project path with namespace (e.g., "gitlab-org/gitlab"). Must contain a slash. Never use just the project name.'
3998
+ ),
3912
3999
  name: z6.string().describe("Skill name to promote"),
3913
4000
  scope: z6.enum(["projects", "groups"]).optional().describe("Scope (default: projects)"),
3914
4001
  group_id: z6.string().optional().describe("Group path (required when scope is groups)")
3915
4002
  },
3916
4003
  execute: async (args) => {
3917
- const auth = ctx.ensureAuth();
3918
- if (!auth) throw new Error("GitLab authentication not available");
4004
+ const auth = authAndValidate(args.project_id);
3919
4005
  const { scope, id } = resolveScope2(args);
3920
4006
  const draftSlug = `${DRAFTS_PREFIX}/${args.name}`;
3921
4007
  const publishedSlug = `${SKILLS_PREFIX}/${args.name}`;