| name | scm-abstraction |
| description | Documentation for the SCM (Source Control Management) abstraction layer in dmtools-agents. Use when configuring GitHub vs Azure DevOps (ADO) as the SCM provider for agents, creating or modifying scm.js providers, setting global or per-agent SCM config, or understanding the provider interface (listPrs, addComment, resolveThread, mergePr, etc.).
|
SCM Abstraction Layer
Overview
js/common/scm.js provides a provider-based SCM abstraction so agents work with
GitHub or Azure DevOps (ADO) without hard-coding github_* tool calls.
agents/js/
common/
scm.js — factory + GithubProvider + AdoProvider (17-method interface)
configLoader.js — loads scm config; re-exports createScm
Configuration
Global — .dmtools/config.js
module.exports = {
scm: { provider: 'ado' },
repository: {
owner: 'MyOrg',
repo: 'my-repo'
}
};
Per-agent — JSON customParams
{
"customParams": {
"scmProvider": "ado"
}
}
Per-agent scmProvider overrides the global config.
Auto-detect from git remote
If owner/repo are not set in config, createScm parses git remote.origin.url
automatically (supports both GitHub and ADO URL formats).
Usage in Agents
var configLoader = require('./configLoader.js');
function action(params) {
var config = configLoader.loadProjectConfig(params.jobParams || params);
var scm = configLoader.createScm(config);
var prs = JSON.parse(scm.listPrs('open'));
if (!prs || prs.length === 0) {
return { success: false, error: 'No open pull requests found' };
}
var pr = prs[0];
var prId = pr.id || pr.number || pr.pullRequestId;
scm.addComment(prId, 'LGTM!');
var discussions = scm.fetchDiscussions(prId);
var rawThreads = (discussions.rawThreads && discussions.rawThreads.threads) || [];
var openThread = rawThreads.find(function(t) { return !t.resolved; });
if (openThread) {
scm.replyToThread(prId, openThread, 'Fixed, thank you!');
scm.resolveThread(prId, openThread);
}
scm.mergePr(prId, 'squash', 'feat: my feature', '');
return { success: true };
}
Provider Interface
Both GithubProvider and AdoProvider implement 17 methods:
| Method | Description | ADO |
|---|
listPrs(state) | List PRs ('open'/'active'/'completed') | ✅ |
getPr(prId) | Get PR details | ✅ |
getPrComments(prId) | Get PR comments | ✅ |
addComment(prId, text) | Post a PR comment | ✅ |
replyToThread(prId, thread, text) | Reply to a review thread | ✅ |
resolveThread(prId, thread) | Resolve/complete a thread | ✅ |
addInlineComment(prId, file, line, text) | Add inline code comment | ✅ |
mergePr(prId, method, title, msg) | Merge a PR | ✅ |
addLabel(prId, label) | Add label/tag | ✅ |
removeLabel(prId, label) | Remove label/tag | ✅ |
getPrDiff(prId) | Get PR diff | ✅ |
fetchDiscussions(prId) | Get review threads (normalised format) | ✅ |
getRemoteRepoInfo() | Parse owner/repo from git remote URL | ✅ |
getJobLogs(jobId, tailLines) | CI job/build logs | ✅ via ado_get_pipeline_logs |
listWorkflowRuns(status, workflowId, limit) | List CI workflow/pipeline runs | ✅ via ado_list_pipeline_runs |
triggerWorkflow(owner, repo, workflow, inputs, ref) | Trigger CI workflow/pipeline | ✅ via ado_trigger_pipeline |
getCommitCheckRuns(sha) | CI check results for a commit | ⚠️ GitHub only (no ADO equivalent) |
ADO pipeline resolution: triggerWorkflow and listWorkflowRuns accept a pipeline name (string) or ID (number) as the workflow/workflowId argument. When a name is passed, the provider calls ado_list_pipelines to resolve it to a numeric ID automatically.
Adding a New Provider
To add GitLab, Bitbucket, or another SCM:
- Add
_createGitlabProvider(workspace, repo) in js/common/scm.js
returning the same 17-method interface object.
- Register it in
createScm:
if (provider === 'gitlab') return _createGitlabProvider(owner, repo);
- Add the URL-parsing pattern in
_detectRepoFromGitRemote:
var gitlabMatch = remoteUrl.match(/gitlab\.com[:/]([^/]+)\/([^/.]+)/);
if (gitlabMatch) return { owner: gitlabMatch[1], repo: gitlabMatch[2].replace('.git', '') };
Testing
The unit test framework in js/unit-tests/ uses a mock scmModule pattern to avoid
real API calls. In tests that need SCM behaviour, provide a mockScmModule:
var mockScmProvider = {
triggerWorkflow: function(owner, repo, workflow, inputs, ref) {
capturedTriggers.push({ owner: owner, repo: repo, workflow: workflow, inputs: inputs, ref: ref });
},
listPrs: function() { return '[]'; },
addComment: function() {},
replyToThread: function() {},
resolveThread: function() {},
mergePr: function() {},
fetchDiscussions: function() { return { markdown: '', rawThreads: [] }; }
};
var mockScmModule = { createScm: function() { return mockScmProvider; } };
var sm = loadModule(
'agents/js/smAgent.js',
makeRequire({ './configLoader.js': freshLoader, './common/scm.js': mockScmModule }),
mocks
);
Run unit tests:
dmtools run agents/js/unit-tests/run_githubHelpers.json
dmtools run agents/js/unit-tests/run_smAgent.json
dmtools run agents/js/unit-tests/run_configLoader.json