opencode-gitlab-dap 1.8.0 → 1.8.2

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
@@ -3611,16 +3611,17 @@ async function searchWikiPages(instanceUrl, token, scope, id, query) {
3611
3611
 
3612
3612
  // src/tools/memory-tools.ts
3613
3613
  var z5 = import_plugin5.tool.schema;
3614
+ var PREFIX = "agents";
3614
3615
  var MEMORY_SLUGS = {
3615
- all: ["memory/facts", "memory/decisions", "memory/patterns"],
3616
- facts: ["memory/facts"],
3617
- decisions: ["memory/decisions"],
3618
- patterns: ["memory/patterns"]
3616
+ all: [`${PREFIX}/memory/facts`, `${PREFIX}/memory/decisions`, `${PREFIX}/memory/patterns`],
3617
+ facts: [`${PREFIX}/memory/facts`],
3618
+ decisions: [`${PREFIX}/memory/decisions`],
3619
+ patterns: [`${PREFIX}/memory/patterns`]
3619
3620
  };
3620
3621
  var RECORD_SLUG = {
3621
- fact: "memory/facts",
3622
- decision: "memory/decisions",
3623
- pattern: "memory/patterns"
3622
+ fact: `${PREFIX}/memory/facts`,
3623
+ decision: `${PREFIX}/memory/decisions`,
3624
+ pattern: `${PREFIX}/memory/patterns`
3624
3625
  };
3625
3626
  function today() {
3626
3627
  return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
@@ -3636,15 +3637,20 @@ async function safeRead(instanceUrl, token, scope, id, slug) {
3636
3637
  return null;
3637
3638
  }
3638
3639
  }
3639
- async function appendToPage(instanceUrl, token, scope, id, slug, newContent, pageTitle) {
3640
+ async function appendToPage(instanceUrl, token, scope, id, slug, newContent) {
3640
3641
  const existing = await safeRead(instanceUrl, token, scope, id, slug);
3641
3642
  if (existing !== null) {
3642
3643
  await updateWikiPage(instanceUrl, token, scope, id, slug, existing + "\n\n" + newContent);
3643
3644
  } else {
3644
- const title = pageTitle ?? slug.split("/").pop() ?? slug;
3645
- await createWikiPage(instanceUrl, token, scope, id, title, newContent);
3645
+ await createWikiPage(instanceUrl, token, scope, id, slug, newContent);
3646
3646
  }
3647
3647
  }
3648
+ function validateProjectId(projectId) {
3649
+ if (!projectId.includes("/")) {
3650
+ 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.`;
3651
+ }
3652
+ return null;
3653
+ }
3648
3654
  function resolveScope(args) {
3649
3655
  if (args.scope === "groups" && args.group_id) {
3650
3656
  return { scope: "groups", id: args.group_id };
@@ -3652,18 +3658,26 @@ function resolveScope(args) {
3652
3658
  return { scope: "projects", id: args.project_id };
3653
3659
  }
3654
3660
  function makeMemoryTools(ctx) {
3661
+ function authAndValidate(projectId) {
3662
+ const auth = ctx.ensureAuth();
3663
+ if (!auth) throw new Error("GitLab authentication not available");
3664
+ const err = validateProjectId(projectId);
3665
+ if (err) throw new Error(err);
3666
+ return auth;
3667
+ }
3655
3668
  return {
3656
3669
  gitlab_memory_load: (0, import_plugin5.tool)({
3657
3670
  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
3671
  args: {
3659
- project_id: z5.string().describe('Project path (e.g., "gitlab-org/gitlab"). Use the same value consistently.'),
3672
+ project_id: z5.string().describe(
3673
+ 'FULL project path with namespace (e.g., "gitlab-org/gitlab"). Must contain a slash. Never use just the project name.'
3674
+ ),
3660
3675
  type: z5.enum(["all", "facts", "decisions", "patterns"]).optional().describe('Which memory to load: "all" (default), "facts", "decisions", or "patterns"'),
3661
3676
  scope: z5.enum(["projects", "groups"]).optional().describe("Scope (default: projects)"),
3662
3677
  group_id: z5.string().optional().describe("Group path (required when scope is groups)")
3663
3678
  },
3664
3679
  execute: async (args) => {
3665
- const auth = ctx.ensureAuth();
3666
- if (!auth) throw new Error("GitLab authentication not available");
3680
+ const auth = authAndValidate(args.project_id);
3667
3681
  const { scope, id } = resolveScope(args);
3668
3682
  const memType = args.type ?? "all";
3669
3683
  const slugs = MEMORY_SLUGS[memType];
@@ -3684,17 +3698,18 @@ ${content}`);
3684
3698
  }
3685
3699
  }),
3686
3700
  gitlab_memory_record: (0, import_plugin5.tool)({
3687
- 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.",
3701
+ 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
3702
  args: {
3689
- 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
+ ),
3690
3706
  type: z5.enum(["fact", "decision", "pattern"]).describe("Type of knowledge to record"),
3691
3707
  content: z5.string().describe("The knowledge to record (markdown)"),
3692
3708
  scope: z5.enum(["projects", "groups"]).optional().describe("Scope (default: projects)"),
3693
3709
  group_id: z5.string().optional().describe("Group path (required when scope is groups)")
3694
3710
  },
3695
3711
  execute: async (args) => {
3696
- const auth = ctx.ensureAuth();
3697
- if (!auth) throw new Error("GitLab authentication not available");
3712
+ const auth = authAndValidate(args.project_id);
3698
3713
  const { scope, id } = resolveScope(args);
3699
3714
  const slug = RECORD_SLUG[args.type];
3700
3715
  if (!slug) return `Unknown memory type: ${args.type}`;
@@ -3722,14 +3737,15 @@ ${args.content}`;
3722
3737
  gitlab_memory_recall: (0, import_plugin5.tool)({
3723
3738
  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
3739
  args: {
3725
- project_id: z5.string().describe('Project path (e.g., "gitlab-org/gitlab"). Use the same value consistently.'),
3740
+ project_id: z5.string().describe(
3741
+ 'FULL project path with namespace (e.g., "gitlab-org/gitlab"). Must contain a slash. Never use just the project name.'
3742
+ ),
3726
3743
  query: z5.string().describe("What to search for"),
3727
3744
  scope: z5.enum(["projects", "groups"]).optional().describe("Scope (default: projects)"),
3728
3745
  group_id: z5.string().optional().describe("Group path (required when scope is groups)")
3729
3746
  },
3730
3747
  execute: async (args) => {
3731
- const auth = ctx.ensureAuth();
3732
- if (!auth) throw new Error("GitLab authentication not available");
3748
+ const auth = authAndValidate(args.project_id);
3733
3749
  const { scope, id } = resolveScope(args);
3734
3750
  try {
3735
3751
  const results = await searchWikiPages(
@@ -3741,7 +3757,7 @@ ${args.content}`;
3741
3757
  );
3742
3758
  if (results.length === 0) return `No knowledge found matching "${args.query}".`;
3743
3759
  return results.map((r) => {
3744
- const category = r.path.startsWith("memory/") ? "memory" : r.path.startsWith("skills") ? "skill" : "other";
3760
+ const category = r.path.includes("/memory/") ? "memory" : r.path.includes("/skills") ? "skill" : "other";
3745
3761
  const snippet = (r.data ?? "").slice(0, 200).replace(/\n/g, " ");
3746
3762
  return `[${category}] ${r.path}: ${snippet}`;
3747
3763
  }).join("\n\n");
@@ -3753,7 +3769,9 @@ ${args.content}`;
3753
3769
  gitlab_memory_log_session: (0, import_plugin5.tool)({
3754
3770
  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
3771
  args: {
3756
- project_id: z5.string().describe('Project path (e.g., "gitlab-org/gitlab"). Use the same value consistently.'),
3772
+ project_id: z5.string().describe(
3773
+ 'FULL project path with namespace (e.g., "gitlab-org/gitlab"). Must contain a slash. Never use just the project name.'
3774
+ ),
3757
3775
  title: z5.string().describe('Brief session title (e.g., "fix-ai-gateway-healthcheck")'),
3758
3776
  summary: z5.string().describe(
3759
3777
  "Session summary in markdown (what happened, what was learned, what went wrong)"
@@ -3762,27 +3780,17 @@ ${args.content}`;
3762
3780
  group_id: z5.string().optional().describe("Group path (required when scope is groups)")
3763
3781
  },
3764
3782
  execute: async (args) => {
3765
- const auth = ctx.ensureAuth();
3766
- if (!auth) throw new Error("GitLab authentication not available");
3783
+ const auth = authAndValidate(args.project_id);
3767
3784
  const { scope, id } = resolveScope(args);
3768
3785
  const date = today();
3769
- const slug = `memory/sessions/${date}-${slugify(args.title)}`;
3770
- const pageTitle = `${date}: ${args.title}`;
3786
+ const slug = `${PREFIX}/memory/sessions/${date}-${slugify(args.title)}`;
3771
3787
  try {
3772
- await createWikiPage(auth.instanceUrl, auth.token, scope, id, pageTitle, args.summary);
3788
+ await createWikiPage(auth.instanceUrl, auth.token, scope, id, slug, args.summary);
3773
3789
  return `Session logged: ${slug}`;
3774
3790
  } catch (err) {
3775
3791
  if (err.message?.includes("already exists") || err.message?.includes("422")) {
3776
3792
  try {
3777
- await updateWikiPage(
3778
- auth.instanceUrl,
3779
- auth.token,
3780
- scope,
3781
- id,
3782
- slug,
3783
- args.summary,
3784
- pageTitle
3785
- );
3793
+ await updateWikiPage(auth.instanceUrl, auth.token, scope, id, slug, args.summary);
3786
3794
  return `Session log updated: ${slug}`;
3787
3795
  } catch (err2) {
3788
3796
  return `Error logging session: ${err2.message}`;
@@ -3798,35 +3806,56 @@ ${args.content}`;
3798
3806
  // src/tools/skill-tools.ts
3799
3807
  var import_plugin6 = require("@opencode-ai/plugin");
3800
3808
  var z6 = import_plugin6.tool.schema;
3809
+ var PREFIX2 = "agents";
3810
+ var SKILLS_PREFIX = `${PREFIX2}/skills`;
3811
+ var DRAFTS_PREFIX = `${PREFIX2}/skills-drafts`;
3801
3812
  function resolveScope2(args) {
3802
3813
  if (args.scope === "groups" && args.group_id) {
3803
3814
  return { scope: "groups", id: args.group_id };
3804
3815
  }
3805
3816
  return { scope: "projects", id: args.project_id };
3806
3817
  }
3818
+ function validateProjectId2(projectId) {
3819
+ if (!projectId.includes("/")) {
3820
+ 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.`;
3821
+ }
3822
+ return null;
3823
+ }
3807
3824
  function makeSkillTools(ctx) {
3825
+ function authAndValidate(projectId) {
3826
+ const auth = ctx.ensureAuth();
3827
+ if (!auth) throw new Error("GitLab authentication not available");
3828
+ const err = validateProjectId2(projectId);
3829
+ if (err) throw new Error(err);
3830
+ return auth;
3831
+ }
3808
3832
  return {
3809
3833
  gitlab_skill_list: (0, import_plugin6.tool)({
3810
3834
  description: "List available project skills and optionally draft skills.\nSkills define step-by-step procedures for common tasks (e.g., incident retros, debugging, deployments).",
3811
3835
  args: {
3812
- project_id: z6.string().describe('Project path (e.g., "gitlab-org/gitlab"). Use the same value consistently.'),
3836
+ project_id: z6.string().describe(
3837
+ 'FULL project path with namespace (e.g., "gitlab-org/gitlab"). Must contain a slash. Never use just the project name.'
3838
+ ),
3813
3839
  include_drafts: z6.boolean().optional().describe("Also list draft skills (default: false)"),
3814
3840
  scope: z6.enum(["projects", "groups"]).optional().describe("Scope (default: projects)"),
3815
3841
  group_id: z6.string().optional().describe("Group path (required when scope is groups)")
3816
3842
  },
3817
3843
  execute: async (args) => {
3818
- const auth = ctx.ensureAuth();
3819
- if (!auth) throw new Error("GitLab authentication not available");
3844
+ const auth = authAndValidate(args.project_id);
3820
3845
  const { scope, id } = resolveScope2(args);
3821
3846
  try {
3822
3847
  const pages = await listWikiPages(auth.instanceUrl, auth.token, scope, id);
3823
- const skills = pages.filter((p) => p.slug.startsWith("skills/") && p.slug !== "skills/index").map((p) => ({ name: p.slug.replace("skills/", ""), title: p.title, draft: false }));
3848
+ const indexSlug = `${SKILLS_PREFIX}/index`;
3849
+ const skills = pages.filter((p) => p.slug.startsWith(`${SKILLS_PREFIX}/`) && p.slug !== indexSlug).map((p) => ({
3850
+ name: p.slug.slice(SKILLS_PREFIX.length + 1),
3851
+ title: p.title,
3852
+ draft: false
3853
+ }));
3824
3854
  let drafts = [];
3825
3855
  if (args.include_drafts) {
3826
- drafts = pages.filter(
3827
- (p) => p.slug.startsWith("skills-drafts/") && p.slug !== "skills-drafts/index"
3828
- ).map((p) => ({
3829
- name: p.slug.replace("skills-drafts/", ""),
3856
+ const draftsIndexSlug = `${DRAFTS_PREFIX}/index`;
3857
+ drafts = pages.filter((p) => p.slug.startsWith(`${DRAFTS_PREFIX}/`) && p.slug !== draftsIndexSlug).map((p) => ({
3858
+ name: p.slug.slice(DRAFTS_PREFIX.length + 1),
3830
3859
  title: p.title,
3831
3860
  draft: true
3832
3861
  }));
@@ -3842,14 +3871,15 @@ function makeSkillTools(ctx) {
3842
3871
  gitlab_skill_load: (0, import_plugin6.tool)({
3843
3872
  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.",
3844
3873
  args: {
3845
- project_id: z6.string().describe('Project path (e.g., "gitlab-org/gitlab"). Use the same value consistently.'),
3874
+ project_id: z6.string().describe(
3875
+ 'FULL project path with namespace (e.g., "gitlab-org/gitlab"). Must contain a slash. Never use just the project name.'
3876
+ ),
3846
3877
  name: z6.string().describe('Skill name (e.g., "incident-retro", "helm-rollback")'),
3847
3878
  scope: z6.enum(["projects", "groups"]).optional().describe("Scope (default: projects)"),
3848
3879
  group_id: z6.string().optional().describe("Group path (required when scope is groups)")
3849
3880
  },
3850
3881
  execute: async (args) => {
3851
- const auth = ctx.ensureAuth();
3852
- if (!auth) throw new Error("GitLab authentication not available");
3882
+ const auth = authAndValidate(args.project_id);
3853
3883
  const { scope, id } = resolveScope2(args);
3854
3884
  try {
3855
3885
  const page = await getWikiPage(
@@ -3857,7 +3887,7 @@ function makeSkillTools(ctx) {
3857
3887
  auth.token,
3858
3888
  scope,
3859
3889
  id,
3860
- `skills/${args.name}`
3890
+ `${SKILLS_PREFIX}/${args.name}`
3861
3891
  );
3862
3892
  return page.content;
3863
3893
  } catch {
@@ -3867,7 +3897,7 @@ function makeSkillTools(ctx) {
3867
3897
  auth.token,
3868
3898
  scope,
3869
3899
  id,
3870
- `skills-drafts/${args.name}`
3900
+ `${DRAFTS_PREFIX}/${args.name}`
3871
3901
  );
3872
3902
  return `[DRAFT SKILL]
3873
3903
 
@@ -3881,7 +3911,9 @@ ${draft.content}`;
3881
3911
  gitlab_skill_save: (0, import_plugin6.tool)({
3882
3912
  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.",
3883
3913
  args: {
3884
- project_id: z6.string().describe('Project path (e.g., "gitlab-org/gitlab"). Use the same value consistently.'),
3914
+ project_id: z6.string().describe(
3915
+ 'FULL project path with namespace (e.g., "gitlab-org/gitlab"). Must contain a slash. Never use just the project name.'
3916
+ ),
3885
3917
  name: z6.string().describe('Skill name (e.g., "incident-retro")'),
3886
3918
  content: z6.string().describe("Skill content in markdown"),
3887
3919
  draft: z6.boolean().optional().describe("Save as draft skill (default: false)"),
@@ -3889,25 +3921,16 @@ ${draft.content}`;
3889
3921
  group_id: z6.string().optional().describe("Group path (required when scope is groups)")
3890
3922
  },
3891
3923
  execute: async (args) => {
3892
- const auth = ctx.ensureAuth();
3893
- if (!auth) throw new Error("GitLab authentication not available");
3924
+ const auth = authAndValidate(args.project_id);
3894
3925
  const { scope, id } = resolveScope2(args);
3895
- const prefix = args.draft ? "skills-drafts" : "skills";
3926
+ const prefix = args.draft ? DRAFTS_PREFIX : SKILLS_PREFIX;
3896
3927
  const slug = `${prefix}/${args.name}`;
3897
3928
  try {
3898
- await updateWikiPage(
3899
- auth.instanceUrl,
3900
- auth.token,
3901
- scope,
3902
- id,
3903
- slug,
3904
- args.content,
3905
- args.name
3906
- );
3929
+ await updateWikiPage(auth.instanceUrl, auth.token, scope, id, slug, args.content);
3907
3930
  return `Updated ${args.draft ? "draft " : ""}skill: ${args.name}`;
3908
3931
  } catch {
3909
3932
  try {
3910
- await createWikiPage(auth.instanceUrl, auth.token, scope, id, args.name, args.content);
3933
+ await createWikiPage(auth.instanceUrl, auth.token, scope, id, slug, args.content);
3911
3934
  return `Created ${args.draft ? "draft " : ""}skill: ${args.name}`;
3912
3935
  } catch (err) {
3913
3936
  return `Error saving skill: ${err.message}`;
@@ -3918,43 +3941,40 @@ ${draft.content}`;
3918
3941
  gitlab_skill_promote: (0, import_plugin6.tool)({
3919
3942
  description: "Promote a draft skill to published.\nMoves the skill from the drafts directory to the published skills directory.",
3920
3943
  args: {
3921
- project_id: z6.string().describe('Project path (e.g., "gitlab-org/gitlab"). Use the same value consistently.'),
3944
+ project_id: z6.string().describe(
3945
+ 'FULL project path with namespace (e.g., "gitlab-org/gitlab"). Must contain a slash. Never use just the project name.'
3946
+ ),
3922
3947
  name: z6.string().describe("Skill name to promote"),
3923
3948
  scope: z6.enum(["projects", "groups"]).optional().describe("Scope (default: projects)"),
3924
3949
  group_id: z6.string().optional().describe("Group path (required when scope is groups)")
3925
3950
  },
3926
3951
  execute: async (args) => {
3927
- const auth = ctx.ensureAuth();
3928
- if (!auth) throw new Error("GitLab authentication not available");
3952
+ const auth = authAndValidate(args.project_id);
3929
3953
  const { scope, id } = resolveScope2(args);
3954
+ const draftSlug = `${DRAFTS_PREFIX}/${args.name}`;
3955
+ const publishedSlug = `${SKILLS_PREFIX}/${args.name}`;
3930
3956
  try {
3931
- const draft = await getWikiPage(
3932
- auth.instanceUrl,
3933
- auth.token,
3934
- scope,
3935
- id,
3936
- `skills-drafts/${args.name}`
3937
- );
3957
+ const draft = await getWikiPage(auth.instanceUrl, auth.token, scope, id, draftSlug);
3938
3958
  try {
3939
3959
  await updateWikiPage(
3940
3960
  auth.instanceUrl,
3941
3961
  auth.token,
3942
3962
  scope,
3943
3963
  id,
3944
- `skills/${args.name}`,
3945
- draft.content,
3946
- args.name
3964
+ publishedSlug,
3965
+ draft.content
3947
3966
  );
3948
3967
  } catch {
3949
- await createWikiPage(auth.instanceUrl, auth.token, scope, id, args.name, draft.content);
3968
+ await createWikiPage(
3969
+ auth.instanceUrl,
3970
+ auth.token,
3971
+ scope,
3972
+ id,
3973
+ publishedSlug,
3974
+ draft.content
3975
+ );
3950
3976
  }
3951
- await deleteWikiPage(
3952
- auth.instanceUrl,
3953
- auth.token,
3954
- scope,
3955
- id,
3956
- `skills-drafts/${args.name}`
3957
- );
3977
+ await deleteWikiPage(auth.instanceUrl, auth.token, scope, id, draftSlug);
3958
3978
  return `Promoted skill "${args.name}" from draft to published.`;
3959
3979
  } catch (err) {
3960
3980
  if (err.message?.includes("not found") || err.message?.includes("404")) {