@asc-agent/runtime 0.7.0 → 0.8.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/README.md +1 -1
- package/dist/adapters/claude-code/guard.js +308 -215
- package/dist/adapters/claude-code/install.d.ts +18 -0
- package/dist/adapters/claude-code/install.js +66 -0
- package/dist/adapters/claude-code/skill.js +114 -18
- package/dist/adapters/gitlab/client.d.ts +10 -0
- package/dist/adapters/gitlab/client.js +17 -1
- package/dist/adapters/gitlab/scm.d.ts +31 -0
- package/dist/adapters/gitlab/scm.js +341 -6
- package/dist/cli/asc.js +902 -74
- package/dist/core/attach/bootstrap.js +4 -4
- package/dist/core/attach/setup.js +8 -8
- package/dist/core/distribution/release.d.ts +3 -3
- package/dist/core/distribution/release.js +1 -1
- package/dist/core/distribution/runtime-install.js +1 -1
- package/dist/core/execution/executor.d.ts +44 -0
- package/dist/core/execution/executor.js +108 -14
- package/dist/core/execution/grant.d.ts +8 -0
- package/dist/core/execution/grant.js +2 -0
- package/dist/core/execution/remote-review.d.ts +117 -0
- package/dist/core/execution/remote-review.js +176 -0
- package/dist/core/model/entities.d.ts +69 -0
- package/dist/core/model/entities.js +46 -0
- package/dist/core/model/transitions.d.ts +1 -1
- package/dist/core/policy/execution-mode.d.ts +136 -0
- package/dist/core/policy/execution-mode.js +127 -0
- package/dist/core/policy/remote-freeze.d.ts +2 -2
- package/dist/ports/scm.d.ts +28 -0
- package/dist/schemas/profile.d.ts +6 -6
- package/package.json +1 -1
|
@@ -111,6 +111,42 @@ function reconcileHooks(settings, specs) {
|
|
|
111
111
|
}
|
|
112
112
|
return { settings: changed.length > 0 ? { ...settings, hooks } : settings, changed };
|
|
113
113
|
}
|
|
114
|
+
/**
|
|
115
|
+
* ASC control-plane 을 Host 권한 계층에서 통과시키는 규칙 (E-02, 2층).
|
|
116
|
+
*
|
|
117
|
+
* 0.7.1 실측에서 raw 외부 write 는 ASC Guard 가 막고, 그 자리의 안전한 출구인
|
|
118
|
+
* `asc grant issue` 는 Host 의 권한 판정이 막았다. 막는 길과 나가는 길이 동시에 닫히면
|
|
119
|
+
* 사람이 갇힌다. Host 안에서 우리가 손댈 수 있는 자리는 이 한 줄뿐이다 — 우리 명령을
|
|
120
|
+
* 명시적으로 허용 목록에 올린다. **허용하는 것은 ASC CLI 뿐이고**, 실행 권한이 있는지는
|
|
121
|
+
* 그 다음에 Core 가 판정한다 (Guard allows, then Core decides).
|
|
122
|
+
*/
|
|
123
|
+
export const CONTROL_PLANE_ALLOW_RULES = ['Bash(asc:*)'];
|
|
124
|
+
/**
|
|
125
|
+
* settings 의 permissions.allow 에 우리 규칙을 **더하기만** 한다. 남의 항목은 읽지도
|
|
126
|
+
* 고치지도 않는다. 이미 있으면 그대로 둔다 — 중복은 그 자체로 결함이다.
|
|
127
|
+
*/
|
|
128
|
+
function reconcileAllow(settings) {
|
|
129
|
+
const permissions = { ...(settings.permissions ?? {}) };
|
|
130
|
+
const allow = [...(permissions.allow ?? [])];
|
|
131
|
+
const added = CONTROL_PLANE_ALLOW_RULES.filter((rule) => !allow.includes(rule));
|
|
132
|
+
if (added.length === 0)
|
|
133
|
+
return { settings, added };
|
|
134
|
+
permissions.allow = [...allow, ...added];
|
|
135
|
+
return { settings: { ...settings, permissions }, added };
|
|
136
|
+
}
|
|
137
|
+
export async function controlPlaneAccess(paths) {
|
|
138
|
+
const settings = (await readJson(locate(paths).settings)) ?? {};
|
|
139
|
+
const permissions = (settings.permissions ?? {});
|
|
140
|
+
const denied = (permissions.deny ?? []).filter((rule) => /^Bash\(\s*asc[\s:)]/.test(rule));
|
|
141
|
+
const allowed = CONTROL_PLANE_ALLOW_RULES.every((rule) => (permissions.allow ?? []).includes(rule));
|
|
142
|
+
if (denied.length > 0)
|
|
143
|
+
return { allowed: false, denied: true, detail: `the host denies ${denied.join(', ')}` };
|
|
144
|
+
return {
|
|
145
|
+
allowed,
|
|
146
|
+
denied: false,
|
|
147
|
+
...(allowed ? {} : { detail: 'the host has no allow rule for ASC commands — `asc refresh` adds it' }),
|
|
148
|
+
};
|
|
149
|
+
}
|
|
114
150
|
async function readJson(path) {
|
|
115
151
|
try {
|
|
116
152
|
return JSON.parse(await readFile(path, 'utf8'));
|
|
@@ -169,6 +205,15 @@ export async function install(paths, now = () => new Date().toISOString(), opts
|
|
|
169
205
|
written.push(`${where.settings} (${reconciled.changed.join(', ')} hook)`);
|
|
170
206
|
}
|
|
171
207
|
manifest.settingsHook = true;
|
|
208
|
+
// control-plane 허용 규칙 (E-02). hook 등록과 같은 파일이지만 다른 계약이다 —
|
|
209
|
+
// hook 은 우리가 막는 자리이고, 이것은 우리가 **막히지 않는** 자리다.
|
|
210
|
+
const allowed = reconcileAllow((await readJson(where.settings)) ?? {});
|
|
211
|
+
if (allowed.added.length > 0) {
|
|
212
|
+
await mkdir(dirname(where.settings), { recursive: true });
|
|
213
|
+
await writeFile(where.settings, JSON.stringify(allowed.settings, null, 2) + '\n', 'utf8');
|
|
214
|
+
written.push(`${where.settings} (control-plane allow: ${allowed.added.join(', ')})`);
|
|
215
|
+
}
|
|
216
|
+
manifest.permissionAllow = [...new Set([...(manifest.permissionAllow ?? []), ...CONTROL_PLANE_ALLOW_RULES])];
|
|
172
217
|
await mkdir(dirname(where.manifest), { recursive: true });
|
|
173
218
|
await writeFile(where.manifest, JSON.stringify(manifest, null, 2) + '\n', 'utf8');
|
|
174
219
|
return { written, skipped, hookRegistered: true };
|
|
@@ -297,6 +342,27 @@ export async function uninstall(paths) {
|
|
|
297
342
|
removed.push(`${where.settings} (${dropped.join(', ')} hook entry)`);
|
|
298
343
|
}
|
|
299
344
|
}
|
|
345
|
+
// 우리가 넣은 허용 규칙만 걷는다 — manifest 에 적힌 것만 대상이므로, 사람이 직접
|
|
346
|
+
// 넣어 둔 같은 규칙을 지우는 일은 생기지 않는다.
|
|
347
|
+
const mine = manifest.permissionAllow ?? [];
|
|
348
|
+
if (mine.length > 0) {
|
|
349
|
+
const current = (await readJson(where.settings)) ?? {};
|
|
350
|
+
const permissions = (current.permissions ?? {});
|
|
351
|
+
const before = permissions.allow ?? [];
|
|
352
|
+
const allow = before.filter((rule) => !mine.includes(rule));
|
|
353
|
+
if (allow.length !== before.length) {
|
|
354
|
+
if (allow.length > 0)
|
|
355
|
+
permissions.allow = allow;
|
|
356
|
+
else
|
|
357
|
+
delete permissions.allow;
|
|
358
|
+
if (Object.keys(permissions).length === 0)
|
|
359
|
+
delete current.permissions;
|
|
360
|
+
else
|
|
361
|
+
current.permissions = permissions;
|
|
362
|
+
await writeFile(where.settings, JSON.stringify(current, null, 2) + '\n', 'utf8');
|
|
363
|
+
removed.push(`${where.settings} (control-plane allow)`);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
300
366
|
await rm(where.manifest, { force: true });
|
|
301
367
|
await rm(dirname(where.manifest), { recursive: true, force: true }).catch(() => { });
|
|
302
368
|
// 파일만 지우면 빈 skills/<name>/ 이 남는다 (P1 관찰 ⑥). 우리가 만든 디렉터리이므로
|
|
@@ -30,8 +30,9 @@ export function skillText() {
|
|
|
30
30
|
name: asc
|
|
31
31
|
description: >-
|
|
32
32
|
Safely start, resume or continue a Logical Session in a project that has ASC attached.
|
|
33
|
-
Triggers — "
|
|
34
|
-
with ASC", "ASC로 진행해", "ASC 세션 이어서", "
|
|
33
|
+
Triggers — "start this work", "continue the ASC session", "publish it", "wrap it up",
|
|
34
|
+
"proceed with ASC", "ASC로 진행해", "ASC 세션 이어서", "이거 작업해", "게시해",
|
|
35
|
+
"마무리해", or the explicit /asc.
|
|
35
36
|
Also triggers on work-status questions in an ASC-attached project — "is this
|
|
36
37
|
implemented?", "what's left to do?", "what should I do now?", "현황 파악",
|
|
37
38
|
"이거 구현됐나", "남은 작업", "지금 뭐 해야 하나" — because answering those by
|
|
@@ -48,7 +49,7 @@ CLI — here you call it, act on the typed outcome, and keep the contract.
|
|
|
48
49
|
|
|
49
50
|
"Is X implemented?", "what's the status of this work?", "what remains?" — in an
|
|
50
51
|
ASC-attached project, do NOT settle these by direct host exploration (git log, tracker
|
|
51
|
-
reads). Run \`asc
|
|
52
|
+
reads). Run \`asc work start <KEY> --json\` first and read its WORK_STATE: it fetches the
|
|
52
53
|
canonical remote, grades the evidence, and refuses to conclude on a stale or key-only
|
|
53
54
|
observation — none of which ad-hoc exploration does. Direct exploration may supplement
|
|
54
55
|
the answer, never replace the judgement. This is mitigation at the model layer; the
|
|
@@ -70,20 +71,85 @@ The user does not need to know this sequence. This is the one the skill follows.
|
|
|
70
71
|
command and ask them to switch this session's permission mode so they can approve the
|
|
71
72
|
single run — a standing allow rule for the command was measured and did not work.
|
|
72
73
|
|
|
73
|
-
1.
|
|
74
|
-
not
|
|
75
|
-
blocked
|
|
76
|
-
2. Anything to run? asc
|
|
74
|
+
1. Where are we? asc status
|
|
75
|
+
not attached → asc setup (it shows profile candidates; a person chooses)
|
|
76
|
+
blocked → show the printed reason and remedy to the person, and stop. Do not open it for them
|
|
77
|
+
2. Anything to run? asc work start [<WORK-KEY>] --json (act per the table below)
|
|
77
78
|
3. Check before handing asc preflight (paths and decision rights, both)
|
|
78
79
|
4. While working asc progress report
|
|
79
80
|
5. Another part's call asc query open / answer
|
|
80
|
-
6.
|
|
81
|
+
6. Send it out asc work publish
|
|
82
|
+
7. Wrap up asc work finish
|
|
81
83
|
\`\`\`
|
|
82
84
|
|
|
83
85
|
**Do not open what is blocked.** LOCK_DRIFT, incomplete configuration and an unreadable
|
|
84
|
-
canonical source are all a person's call, and \`
|
|
86
|
+
canonical source are all a person's call, and \`asc status\` already states the reason
|
|
85
87
|
and the remedy.
|
|
86
88
|
|
|
89
|
+
## Three axes, and they do not stand in for each other
|
|
90
|
+
|
|
91
|
+
\`\`\`text
|
|
92
|
+
Agent Management who owns this work, what its scope is, how far it has come
|
|
93
|
+
Decision Authority whether a person has to decide this
|
|
94
|
+
Execution Mode who runs the approved act — MANUAL (a person) or AUTO (ASC)
|
|
95
|
+
\`\`\`
|
|
96
|
+
|
|
97
|
+
**AUTO is not the opposite of human-in-the-loop.** It does not approve anything on a
|
|
98
|
+
person's behalf, and MANUAL does not mean something was already approved. Changing the
|
|
99
|
+
mode never changes a session's owner, scope, progress or handoff.
|
|
100
|
+
|
|
101
|
+
\`\`\`text
|
|
102
|
+
"수동으로 해" · "let me run it" → asc mode manual
|
|
103
|
+
"ASC가 자동으로 관리해" → asc mode auto
|
|
104
|
+
\`\`\`
|
|
105
|
+
|
|
106
|
+
Stepping down out of AUTO is one command and it is recorded — who said so, and when.
|
|
107
|
+
**Do not use it to get past a block.** If the guard stopped a write, the answer is
|
|
108
|
+
\`asc work publish\`, not a mode change; flipping to MANUAL to push raw is the exact drift
|
|
109
|
+
ASC exists to make visible, and the record makes it visible to the person you work with.
|
|
110
|
+
|
|
111
|
+
**A workspace nobody has chosen a mode for is not in AUTO.** AUTO exists only where a
|
|
112
|
+
person turned it on and the checks below passed, so a fresh or upgraded
|
|
113
|
+
workspace enforces nothing until someone says so. \`asc status\` says which of the two it is.
|
|
114
|
+
|
|
115
|
+
\`asc mode auto\` refuses unless three things hold here: a managed write path assembles, the
|
|
116
|
+
guard is installed, and ASC's own commands still run in this host. Whether a *particular*
|
|
117
|
+
action can go out is answered when that action is attempted, not now. If ASC's own
|
|
118
|
+
enforcement is in the way, the exit is \`asc mode manual\`.
|
|
119
|
+
|
|
120
|
+
**A workspace whose mode cannot be read is not MANUAL.** If the record is there but broken,
|
|
121
|
+
raw external writes stay blocked and \`asc status\` names the reason
|
|
122
|
+
(\`MODE_STATE_UNREADABLE\` / \`MODE_STATE_INVALID\`). ASC's own commands still run, and that
|
|
123
|
+
is how it gets fixed.
|
|
124
|
+
|
|
125
|
+
## Whose decision is which
|
|
126
|
+
|
|
127
|
+
\`\`\`text
|
|
128
|
+
the project's rules what has to be done, on which branch, in what order
|
|
129
|
+
ASC who owns the work · what a person must decide · whether this
|
|
130
|
+
action is executable against the remote right now · execution · audit
|
|
131
|
+
\`\`\`
|
|
132
|
+
|
|
133
|
+
ASC does not own a project's workflow. If a repository's own conventions say a branch is
|
|
134
|
+
updated a certain way, that judgement belongs to those conventions — ASC checks that the
|
|
135
|
+
action they chose is possible against the current remote, not whether it was the right
|
|
136
|
+
action. A canonical source is what judgement is measured against; it is not a list of the
|
|
137
|
+
only branches that may be written.
|
|
138
|
+
|
|
139
|
+
## Maintenance — the same words in both products
|
|
140
|
+
|
|
141
|
+
\`\`\`text
|
|
142
|
+
"업데이트해" → asc update · jam update
|
|
143
|
+
"적용 상태 다시 맞춰" → asc refresh · jam refresh
|
|
144
|
+
"지금 상태 어때" → asc status · jam status
|
|
145
|
+
"제거해" → asc uninstall · jam uninstall
|
|
146
|
+
\`\`\`
|
|
147
|
+
|
|
148
|
+
\`update\` moves to a new published version. \`refresh\` keeps the version and re-converges
|
|
149
|
+
only what this runtime installed — it never re-infers the profile, the workspace, the
|
|
150
|
+
identity or the bindings, and it never touches sessions. \`uninstall\` removes the product
|
|
151
|
+
and leaves every bit of your state where it is.
|
|
152
|
+
|
|
87
153
|
## "Update ASC" — one command per product, no questions
|
|
88
154
|
|
|
89
155
|
Each product updates itself. ASC never updates JAM and JAM never updates ASC; the person
|
|
@@ -160,9 +226,10 @@ report as one.
|
|
|
160
226
|
- **Receiving a DECIDE creates no approval, authority or scope.** If a human decision is
|
|
161
227
|
needed, use that answer as evidence and raise it to a person.
|
|
162
228
|
|
|
163
|
-
5. Pausing: \`asc
|
|
164
|
-
Finishing: \`asc
|
|
165
|
-
|
|
229
|
+
5. Pausing: \`asc work pause <S-ID> --position "<how far>" --next "<next action>"\`.
|
|
230
|
+
Finishing: \`asc work finish <S-ID> --verified "<what the self-check covered>" --next "<next>"\`.
|
|
231
|
+
One command finishes it: the handoff is written, the physical binding is released, the
|
|
232
|
+
Controller collects, and the session is archived. Do not make a person run two steps.
|
|
166
233
|
|
|
167
234
|
## "Publish it" — what a session produced, going out
|
|
168
235
|
|
|
@@ -170,14 +237,31 @@ The person says *publish it* · *open the MR* · *get it onto develop*. That sen
|
|
|
170
237
|
approval, and it is not asked for twice. It is also not wider than itself: **"open the MR"
|
|
171
238
|
is not "approve the merge"**.
|
|
172
239
|
|
|
173
|
-
Nothing reaches an external system except through
|
|
174
|
-
|
|
240
|
+
Nothing reaches an external system except through an approved grant. One command carries
|
|
241
|
+
that whole path — read-only review, decision authority, grant, atomic claim, revalidation,
|
|
242
|
+
exactly one write, read-back, audit:
|
|
175
243
|
|
|
176
244
|
\`\`\`text
|
|
177
|
-
asc
|
|
178
|
-
asc
|
|
245
|
+
asc work publish [<S-ID>] --action <key> --target <ref> --body-file <path> --as <actor>
|
|
246
|
+
asc work publish … --review # read the facts and stop. Nothing goes out
|
|
179
247
|
\`\`\`
|
|
180
248
|
|
|
249
|
+
The review is **not a second approval**. The person's instruction already settled who
|
|
250
|
+
decides; the review settles facts — is this the remote this work is bound to, is the commit
|
|
251
|
+
that was approved still the commit that is here, is there already an open change for it. A
|
|
252
|
+
\`NOT_EXECUTABLE\` means the action does not hold as it stands; a \`REVIEW_REQUIRED\` means a
|
|
253
|
+
person has to look at something the facts cannot settle. Neither is a request to re-approve.
|
|
254
|
+
|
|
255
|
+
Afterwards the result is read back. If what comes back differs from what was expected the
|
|
256
|
+
outcome is \`NOT_VERIFIED\` — the write happened, it is not a success, and the grant is
|
|
257
|
+
spent either way. If the outcome could not be determined at all it is \`UNCERTAIN\`: read
|
|
258
|
+
the remote before doing anything else, and never repeat the command.
|
|
259
|
+
|
|
260
|
+
Being bound to one repository is the scope of managed execution, not a preference. If the
|
|
261
|
+
target is a different repository the answer is \`REVIEW_REQUIRED\`, and approving the action
|
|
262
|
+
again does not change it — what has to change is the binding, and that is a separate
|
|
263
|
+
decision.
|
|
264
|
+
|
|
181
265
|
The action key is the provider's (\`gitlab.mr.create\`, \`gitlab.note.create\`, \`git.push\`,
|
|
182
266
|
\`coordination.publish\`, \`github.issue_comment.create\`). If nothing bound to this
|
|
183
267
|
workspace can carry out that action, issuing **fails there** rather than after the person
|
|
@@ -190,8 +274,14 @@ Publishing a coordination question is the same shape — \`asc coordination publ
|
|
|
190
274
|
<G-ID> --query <X-ID> …\`. Reading (\`coordination status\`, \`coordination observe\`) needs
|
|
191
275
|
no grant.
|
|
192
276
|
|
|
193
|
-
**Never** reach for \`git push\`, \`glab\`, or \`gh\` directly
|
|
194
|
-
being stopped is not a puzzle to solve — it means the act belongs in
|
|
277
|
+
**Never** reach for \`git push\`, \`glab\`, or \`gh\` directly while the mode is AUTO. The
|
|
278
|
+
guard stops those, and being stopped is not a puzzle to solve — it means the act belongs in
|
|
279
|
+
\`asc work publish\`. ASC's own commands are never blocked by that guard, so the way out is
|
|
280
|
+
always an asc command, never uninstalling the hook.
|
|
281
|
+
|
|
282
|
+
In MANUAL the guard blocks none of it. It leaves one line saying the write is leaving the
|
|
283
|
+
managed path and pointing at \`asc work publish --review\`; what to do about that is the
|
|
284
|
+
person's call, and the project's rules are what answer it.
|
|
195
285
|
|
|
196
286
|
## Progress reporting
|
|
197
287
|
|
|
@@ -230,6 +320,12 @@ A change that gives a person no reason to look again is noise, not a report.
|
|
|
230
320
|
portable command is the bootstrap one
|
|
231
321
|
- Investigate external situations directly — leave reading thread originals to \`asc-inbox\` and take back only what it summarised
|
|
232
322
|
- Declare that I verified what I built — independent verification is \`asc-review\`
|
|
323
|
+
- Teach a person the internal order (session issue · host bind · grant issue · controller
|
|
324
|
+
collect). On a healthy path they type none of those — \`work start\`, \`work publish\`,
|
|
325
|
+
\`work finish\` cover it
|
|
326
|
+
- Treat AUTO as permission, or MANUAL as approval. Neither mode decides what a person
|
|
327
|
+
must decide
|
|
328
|
+
- Turn enforcement off by removing the product. \`asc mode manual\` is the exit
|
|
233
329
|
`;
|
|
234
330
|
}
|
|
235
331
|
export function inboxSkillText() {
|
|
@@ -33,6 +33,7 @@ export declare class GlabApiClient implements GitLabReader, GitLabWriter {
|
|
|
33
33
|
constructor(run: ProcessRunner);
|
|
34
34
|
get<T>(path: string): Promise<GitLabResponse<T>>;
|
|
35
35
|
post<T>(path: string, body: Record<string, unknown>): Promise<GitLabResponse<T>>;
|
|
36
|
+
put<T>(path: string, body: Record<string, unknown>): Promise<GitLabResponse<T>>;
|
|
36
37
|
}
|
|
37
38
|
/**
|
|
38
39
|
* 읽기 통로. GitLabClient(토큰)와 GlabApiClient(로그인된 도구) 둘 다 이것이다 —
|
|
@@ -50,6 +51,14 @@ export declare class GlabApiClient implements GitLabReader, GitLabWriter {
|
|
|
50
51
|
*/
|
|
51
52
|
export interface GitLabWriter {
|
|
52
53
|
post<T>(path: string, body: Record<string, unknown>): Promise<GitLabResponse<T>>;
|
|
54
|
+
/**
|
|
55
|
+
* GitLab 의 merge 는 `PUT /merge_requests/:iid/merge` 다 (공식 계약).
|
|
56
|
+
*
|
|
57
|
+
* 예전에는 POST 로 보냈다. 그 경로는 승인이 끝난 뒤에 실패하고, 실패한 것이 정말 나가지
|
|
58
|
+
* 않았는지는 그 자리에서 알 수 없다 — 통로가 없는 것과 잘못된 통로로 부르는 것은 다르고,
|
|
59
|
+
* 후자가 더 나쁘다. 통로가 없으면 없다고 말할 수 있게 optional 로 둔다.
|
|
60
|
+
*/
|
|
61
|
+
put?<T>(path: string, body: Record<string, unknown>): Promise<GitLabResponse<T>>;
|
|
53
62
|
}
|
|
54
63
|
export interface GitLabReader {
|
|
55
64
|
get<T>(path: string): Promise<GitLabResponse<T>>;
|
|
@@ -59,6 +68,7 @@ export declare class GitLabClient implements GitLabReader, GitLabWriter {
|
|
|
59
68
|
constructor(deps: GitLabClientDeps);
|
|
60
69
|
get<T>(path: string): Promise<GitLabResponse<T>>;
|
|
61
70
|
post<T>(path: string, body: Record<string, unknown>): Promise<GitLabResponse<T>>;
|
|
71
|
+
put<T>(path: string, body: Record<string, unknown>): Promise<GitLabResponse<T>>;
|
|
62
72
|
}
|
|
63
73
|
/** `group/sub/project!19` 또는 `group/project#7` 을 쪼갠다. */
|
|
64
74
|
export declare function parseRef(reference: string): {
|
|
@@ -51,6 +51,16 @@ export class GlabApiClient {
|
|
|
51
51
|
return { ok: false, status: 0, data: null, error: String(error.message ?? error).slice(0, 200) };
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
|
+
async put(path, body) {
|
|
55
|
+
const fields = Object.entries(body).flatMap(([key, value]) => value === undefined ? [] : ['-f', `${key}=${String(value)}`]);
|
|
56
|
+
try {
|
|
57
|
+
const stdout = await this.#run('glab', ['api', '--method', 'PUT', path.replace(/^\//, ''), ...fields]);
|
|
58
|
+
return { ok: true, status: 200, data: JSON.parse(stdout) };
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
return { ok: false, status: 0, data: null, error: String(error.message ?? error).slice(0, 200) };
|
|
62
|
+
}
|
|
63
|
+
}
|
|
54
64
|
}
|
|
55
65
|
export class GitLabClient {
|
|
56
66
|
#token;
|
|
@@ -78,8 +88,14 @@ export class GitLabClient {
|
|
|
78
88
|
};
|
|
79
89
|
}
|
|
80
90
|
async post(path, body) {
|
|
91
|
+
return this.#write('POST', path, body);
|
|
92
|
+
}
|
|
93
|
+
async put(path, body) {
|
|
94
|
+
return this.#write('PUT', path, body);
|
|
95
|
+
}
|
|
96
|
+
async #write(method, path, body) {
|
|
81
97
|
const response = await this.#fetch(`${this.#baseUrl}${path}`, {
|
|
82
|
-
method
|
|
98
|
+
method,
|
|
83
99
|
headers: {
|
|
84
100
|
accept: 'application/json',
|
|
85
101
|
'content-type': 'application/json',
|
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
import type { CanonicalSnapshot } from '../../core/model/entities.ts';
|
|
2
|
+
import type { RemoteFacts } from '../../core/execution/remote-review.ts';
|
|
2
3
|
import type { BaselineQuery, ExternalAction, ExternalActionResult, ScmPort, ThreadSnapshot } from '../../ports/scm.ts';
|
|
3
4
|
import { type GitLabReader, type GitLabWriter } from './client.ts';
|
|
4
5
|
/** 이 adapter 가 수행할 수 있는 행위. 목록에 없는 것은 하지 않는다. */
|
|
5
6
|
export declare const GITLAB_ACTIONS: readonly ["coordination.publish", "gitlab.note.create", "gitlab.mr.create", "gitlab.mr.merge", "gitlab.issue.update", "git.push"];
|
|
7
|
+
/**
|
|
8
|
+
* 되돌려 읽을 수 있는 행위 (0.8.0 보정 P1-2).
|
|
9
|
+
*
|
|
10
|
+
* `coordination.publish` 가 여기 없는 이유는 그 실행이 이 통로가 아니라 조율 표면에서
|
|
11
|
+
* 나가기 때문이다 — 자기가 하지 않은 일을 확인했다고 말하지 않는다.
|
|
12
|
+
*/
|
|
13
|
+
export declare const VERIFIABLE_ACTIONS: readonly ["git.push", "gitlab.mr.create", "gitlab.mr.merge", "gitlab.note.create", "gitlab.issue.update"];
|
|
6
14
|
export type GitLabScmDeps = {
|
|
7
15
|
reader: GitLabReader;
|
|
8
16
|
writer: GitLabWriter;
|
|
@@ -14,6 +22,8 @@ export type GitLabScmDeps = {
|
|
|
14
22
|
}>>;
|
|
15
23
|
/** `git.push` 를 수행할 자리. 없으면 push 는 할 수 없다고 답한다. */
|
|
16
24
|
repoRoot?: string;
|
|
25
|
+
/** 이 결합이 가리키는 원격 이름. 검수가 URL·신원을 읽을 자리다. */
|
|
26
|
+
remoteName?: string;
|
|
17
27
|
/** 테스트가 실제 git 을 부르지 않게 하는 통로. */
|
|
18
28
|
git?: (args: readonly string[], cwd: string) => Promise<{
|
|
19
29
|
ok: boolean;
|
|
@@ -39,5 +49,26 @@ export declare class GitLabScm implements ScmPort {
|
|
|
39
49
|
*/
|
|
40
50
|
/** 이 통로가 아는 행위. execute 의 분기와 같은 목록이어야 한다. */
|
|
41
51
|
supports(action: string): boolean;
|
|
52
|
+
/**
|
|
53
|
+
* 나가기 **전에** 읽히는 사실 (0.8.0 §D·§L·§M·§N). 판정은 하지 않는다 — Core 의
|
|
54
|
+
* Remote Review 가 이 사실로 판정한다. 여기서 쓰는 것은 하나도 없다.
|
|
55
|
+
*/
|
|
56
|
+
review(action: ExternalAction): Promise<RemoteFacts>;
|
|
57
|
+
/** 되돌려 읽을 수 있는 행위. `execute` 의 분기와 이 목록이 갈리면 그것이 결함이다. */
|
|
58
|
+
verifies(action: string): boolean;
|
|
59
|
+
/**
|
|
60
|
+
* 나간 **뒤에** 밖에서 읽히는 사실. 명령이 0 으로 끝났다는 것은 성공이 아니다.
|
|
61
|
+
*/
|
|
62
|
+
verify(action: ExternalAction, result: {
|
|
63
|
+
resultRef: string;
|
|
64
|
+
}): Promise<{
|
|
65
|
+
observed: Record<string, string | undefined>;
|
|
66
|
+
unsupported?: boolean;
|
|
67
|
+
}>;
|
|
42
68
|
execute(action: ExternalAction): Promise<ExternalActionResult>;
|
|
43
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* 원격 URL 에서 프로젝트 신원만 꺼낸다 — `git@host:group/p.git` 도 `https://host/group/p` 도
|
|
72
|
+
* 같은 `group/p` 다. 결합과 견주는 값이므로 형태가 아니라 신원이어야 한다.
|
|
73
|
+
*/
|
|
74
|
+
export declare function identityOf(url: string): string;
|