@asc-agent/runtime 0.1.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/LICENSE +15 -0
- package/README.md +22 -0
- package/dist/adapters/claude-code/binding.d.ts +5 -0
- package/dist/adapters/claude-code/binding.js +14 -0
- package/dist/adapters/claude-code/guard.d.ts +54 -0
- package/dist/adapters/claude-code/guard.js +295 -0
- package/dist/adapters/claude-code/install.d.ts +67 -0
- package/dist/adapters/claude-code/install.js +231 -0
- package/dist/adapters/claude-code/observer.d.ts +38 -0
- package/dist/adapters/claude-code/observer.js +77 -0
- package/dist/adapters/claude-code/probe.d.ts +53 -0
- package/dist/adapters/claude-code/probe.js +129 -0
- package/dist/adapters/claude-code/skill.d.ts +8 -0
- package/dist/adapters/claude-code/skill.js +267 -0
- package/dist/adapters/fixture-work/index.d.ts +34 -0
- package/dist/adapters/fixture-work/index.js +78 -0
- package/dist/adapters/github/adapter.d.ts +32 -0
- package/dist/adapters/github/adapter.js +95 -0
- package/dist/adapters/github/client.d.ts +30 -0
- package/dist/adapters/github/client.js +90 -0
- package/dist/adapters/github/context.d.ts +44 -0
- package/dist/adapters/github/context.js +170 -0
- package/dist/adapters/github/event-source.d.ts +55 -0
- package/dist/adapters/github/event-source.js +183 -0
- package/dist/adapters/github/scm.d.ts +50 -0
- package/dist/adapters/github/scm.js +104 -0
- package/dist/adapters/gitlab/adapter.d.ts +36 -0
- package/dist/adapters/gitlab/adapter.js +117 -0
- package/dist/adapters/gitlab/client.d.ts +30 -0
- package/dist/adapters/gitlab/client.js +47 -0
- package/dist/adapters/gitlab/ports.d.ts +40 -0
- package/dist/adapters/gitlab/ports.js +186 -0
- package/dist/adapters/jam/adapter.d.ts +53 -0
- package/dist/adapters/jam/adapter.js +144 -0
- package/dist/adapters/jam/event-source.d.ts +28 -0
- package/dist/adapters/jam/event-source.js +65 -0
- package/dist/adapters/jam/mcp-client.d.ts +50 -0
- package/dist/adapters/jam/mcp-client.js +218 -0
- package/dist/adapters/jam/ports.d.ts +51 -0
- package/dist/adapters/jam/ports.js +167 -0
- package/dist/adapters/local/identity.d.ts +27 -0
- package/dist/adapters/local/identity.js +40 -0
- package/dist/adapters/local/presentation.d.ts +14 -0
- package/dist/adapters/local/presentation.js +42 -0
- package/dist/adapters/markdown/layout.d.ts +15 -0
- package/dist/adapters/markdown/layout.js +49 -0
- package/dist/adapters/markdown/serialize.d.ts +9 -0
- package/dist/adapters/markdown/serialize.js +120 -0
- package/dist/adapters/markdown/state-store.d.ts +23 -0
- package/dist/adapters/markdown/state-store.js +316 -0
- package/dist/adapters/mattermost/client.d.ts +23 -0
- package/dist/adapters/mattermost/client.js +54 -0
- package/dist/adapters/mattermost/presentation.d.ts +43 -0
- package/dist/adapters/mattermost/presentation.js +88 -0
- package/dist/adapters/memory/mocks.d.ts +87 -0
- package/dist/adapters/memory/mocks.js +163 -0
- package/dist/adapters/memory/runtime-binding.d.ts +23 -0
- package/dist/adapters/memory/runtime-binding.js +118 -0
- package/dist/adapters/memory/state-store.d.ts +17 -0
- package/dist/adapters/memory/state-store.js +125 -0
- package/dist/adapters/text/renderer.d.ts +7 -0
- package/dist/adapters/text/renderer.js +81 -0
- package/dist/adapters/webhook/ingress.d.ts +103 -0
- package/dist/adapters/webhook/ingress.js +150 -0
- package/dist/cli/asc.d.ts +16 -0
- package/dist/cli/asc.js +2914 -0
- package/dist/cli/identity-config.d.ts +7 -0
- package/dist/cli/identity-config.js +31 -0
- package/dist/composition/observe.d.ts +21 -0
- package/dist/composition/observe.js +72 -0
- package/dist/composition/registry.d.ts +33 -0
- package/dist/composition/registry.js +62 -0
- package/dist/composition/runtime.d.ts +63 -0
- package/dist/composition/runtime.js +155 -0
- package/dist/core/approval/service.d.ts +17 -0
- package/dist/core/approval/service.js +129 -0
- package/dist/core/attach/bootstrap.d.ts +102 -0
- package/dist/core/attach/bootstrap.js +178 -0
- package/dist/core/attach/init.d.ts +29 -0
- package/dist/core/attach/init.js +100 -0
- package/dist/core/attach/setup-plan.d.ts +125 -0
- package/dist/core/attach/setup-plan.js +177 -0
- package/dist/core/attach/setup.d.ts +40 -0
- package/dist/core/attach/setup.js +140 -0
- package/dist/core/binding/types.d.ts +93 -0
- package/dist/core/binding/types.js +74 -0
- package/dist/core/distribution/release.d.ts +15 -0
- package/dist/core/distribution/release.js +27 -0
- package/dist/core/distribution/runtime-install.d.ts +50 -0
- package/dist/core/distribution/runtime-install.js +90 -0
- package/dist/core/distribution/runtime-select.d.ts +74 -0
- package/dist/core/distribution/runtime-select.js +149 -0
- package/dist/core/execution/executor.d.ts +51 -0
- package/dist/core/execution/executor.js +106 -0
- package/dist/core/execution/grant.d.ts +42 -0
- package/dist/core/execution/grant.js +78 -0
- package/dist/core/model/entities.d.ts +872 -0
- package/dist/core/model/entities.js +285 -0
- package/dist/core/model/ids.d.ts +41 -0
- package/dist/core/model/ids.js +50 -0
- package/dist/core/model/transitions.d.ts +26 -0
- package/dist/core/model/transitions.js +124 -0
- package/dist/core/monitor/coverage.d.ts +111 -0
- package/dist/core/monitor/coverage.js +129 -0
- package/dist/core/monitor/engine.d.ts +134 -0
- package/dist/core/monitor/engine.js +574 -0
- package/dist/core/monitor/health-alerts.d.ts +35 -0
- package/dist/core/monitor/health-alerts.js +88 -0
- package/dist/core/monitor/investigation.d.ts +82 -0
- package/dist/core/monitor/investigation.js +232 -0
- package/dist/core/monitor/observation.d.ts +79 -0
- package/dist/core/monitor/observation.js +110 -0
- package/dist/core/monitor/relevance.d.ts +47 -0
- package/dist/core/monitor/relevance.js +100 -0
- package/dist/core/monitor/signals.d.ts +59 -0
- package/dist/core/monitor/signals.js +105 -0
- package/dist/core/operator/local-operator.d.ts +60 -0
- package/dist/core/operator/local-operator.js +82 -0
- package/dist/core/operator/preflight.d.ts +60 -0
- package/dist/core/operator/preflight.js +139 -0
- package/dist/core/operator/proceed.d.ts +98 -0
- package/dist/core/operator/proceed.js +167 -0
- package/dist/core/operator/progress.d.ts +124 -0
- package/dist/core/operator/progress.js +135 -0
- package/dist/core/operator/render.d.ts +34 -0
- package/dist/core/operator/render.js +170 -0
- package/dist/core/operator/runtime-binding.d.ts +61 -0
- package/dist/core/operator/runtime-binding.js +22 -0
- package/dist/core/policy/ownership.d.ts +44 -0
- package/dist/core/policy/ownership.js +46 -0
- package/dist/core/policy/policy.d.ts +99 -0
- package/dist/core/policy/policy.js +147 -0
- package/dist/core/policy/remote-freeze.d.ts +101 -0
- package/dist/core/policy/remote-freeze.js +151 -0
- package/dist/core/policy/scope.d.ts +23 -0
- package/dist/core/policy/scope.js +93 -0
- package/dist/core/presentation/digest.d.ts +98 -0
- package/dist/core/presentation/digest.js +160 -0
- package/dist/core/resolver/load.d.ts +121 -0
- package/dist/core/resolver/load.js +246 -0
- package/dist/core/resolver/render.d.ts +4 -0
- package/dist/core/resolver/render.js +62 -0
- package/dist/core/resolver/resolve.d.ts +42 -0
- package/dist/core/resolver/resolve.js +51 -0
- package/dist/core/resolver/version.d.ts +22 -0
- package/dist/core/resolver/version.js +49 -0
- package/dist/core/runtime/audit.d.ts +374 -0
- package/dist/core/runtime/audit.js +454 -0
- package/dist/core/runtime/claims.d.ts +115 -0
- package/dist/core/runtime/claims.js +153 -0
- package/dist/core/runtime/closure.d.ts +73 -0
- package/dist/core/runtime/closure.js +162 -0
- package/dist/core/runtime/controller.d.ts +40 -0
- package/dist/core/runtime/controller.js +121 -0
- package/dist/core/runtime/escalation.d.ts +188 -0
- package/dist/core/runtime/escalation.js +322 -0
- package/dist/core/runtime/execution-state.d.ts +43 -0
- package/dist/core/runtime/execution-state.js +81 -0
- package/dist/core/runtime/front.d.ts +95 -0
- package/dist/core/runtime/front.js +144 -0
- package/dist/core/runtime/orchestrator.d.ts +54 -0
- package/dist/core/runtime/orchestrator.js +98 -0
- package/dist/core/runtime/query.d.ts +184 -0
- package/dist/core/runtime/query.js +213 -0
- package/dist/core/runtime/report.d.ts +33 -0
- package/dist/core/runtime/report.js +108 -0
- package/dist/core/runtime/session.d.ts +156 -0
- package/dist/core/runtime/session.js +281 -0
- package/dist/core/runtime/store-ops.d.ts +22 -0
- package/dist/core/runtime/store-ops.js +26 -0
- package/dist/core/view/build-view.d.ts +36 -0
- package/dist/core/view/build-view.js +131 -0
- package/dist/core/view/decision-view.d.ts +564 -0
- package/dist/core/view/decision-view.js +103 -0
- package/dist/core/workspace/identity.d.ts +82 -0
- package/dist/core/workspace/identity.js +133 -0
- package/dist/core/workspace/index-store.d.ts +140 -0
- package/dist/core/workspace/index-store.js +125 -0
- package/dist/core/workspace/migrate.d.ts +73 -0
- package/dist/core/workspace/migrate.js +123 -0
- package/dist/core/workspace/resolve.d.ts +34 -0
- package/dist/core/workspace/resolve.js +89 -0
- package/dist/ports/adapter.d.ts +35 -0
- package/dist/ports/adapter.js +12 -0
- package/dist/ports/approval.d.ts +98 -0
- package/dist/ports/approval.js +7 -0
- package/dist/ports/change-context.d.ts +22 -0
- package/dist/ports/change-context.js +8 -0
- package/dist/ports/event-source.d.ts +42 -0
- package/dist/ports/event-source.js +6 -0
- package/dist/ports/inventory.d.ts +53 -0
- package/dist/ports/inventory.js +12 -0
- package/dist/ports/presentation.d.ts +62 -0
- package/dist/ports/presentation.js +13 -0
- package/dist/ports/renderer.d.ts +23 -0
- package/dist/ports/renderer.js +7 -0
- package/dist/ports/resource-context.d.ts +51 -0
- package/dist/ports/resource-context.js +9 -0
- package/dist/ports/scm.d.ts +40 -0
- package/dist/ports/scm.js +6 -0
- package/dist/ports/state-store.d.ts +97 -0
- package/dist/ports/state-store.js +12 -0
- package/dist/presets/balanced.json +8 -0
- package/dist/presets/conservative.json +9 -0
- package/dist/presets/lightweight.json +8 -0
- package/dist/profiles/example-team/profile.json +101 -0
- package/dist/profiles/pilot-local/profile.json +56 -0
- package/dist/schemas/profile.d.ts +631 -0
- package/dist/schemas/profile.js +287 -0
- package/package.json +55 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
// Setup — detect → plan → apply → verify (C-14 §6).
|
|
2
|
+
//
|
|
3
|
+
// **하나의 판단을 두 진입이 나눠 쓴다.** 사람이 보는 화면과 agent가 파싱하는 JSON은
|
|
4
|
+
// 표현이 다를 뿐 같은 plan에서 나온다. 둘이 각자 판단하면 그 둘은 반드시 갈라진다.
|
|
5
|
+
//
|
|
6
|
+
// 이 파일은 **순수하다** — network·subprocess·clock·파일에 손대지 않는다. 세상의 사실은
|
|
7
|
+
// caller가 관측해 `SetupState` 로 넘긴다. 그래야 "이 명령이 무엇을 바꿀 것인가"를
|
|
8
|
+
// 아무것도 바꾸지 않고 물어볼 수 있다.
|
|
9
|
+
import { portableCommand, RUNTIME_PACKAGE, shorthandCommand } from "../distribution/release.js";
|
|
10
|
+
/**
|
|
11
|
+
* 무엇을 바꿀 것인가. **아무것도 바꾸지 않는다.**
|
|
12
|
+
*
|
|
13
|
+
* 사람이 답해야 하는 것(profile 선택·project 채택·설치물 수정)은 여기서 멈춘다 —
|
|
14
|
+
* agent라고 해서 대신 추측하지 않는다 (C-13 · C-14 §7.1).
|
|
15
|
+
*/
|
|
16
|
+
export function computeSetupPlan(state) {
|
|
17
|
+
const evidence = [
|
|
18
|
+
`project=${state.projectRoot}`,
|
|
19
|
+
state.git ? 'git=yes' : 'git=no',
|
|
20
|
+
state.ascRoot ? `attached=${state.ascRoot}` : 'attached=no',
|
|
21
|
+
`scope=${state.scope}`,
|
|
22
|
+
];
|
|
23
|
+
// 지금 명령이 어디서 도는가. 설치된 `asc` 가 없으면 bootstrap이고, 그때 agent에게
|
|
24
|
+
// `asc …` 를 실행하라고 주면 안 된다 (C-14 §3.4 · 불변식 ⑯).
|
|
25
|
+
const mode = state.stableRuntime?.status === 'CURRENT' ? 'installed-runtime' : 'bootstrap';
|
|
26
|
+
const command = (args) => ({
|
|
27
|
+
display: shorthandCommand(args),
|
|
28
|
+
portable: mode === 'installed-runtime' ? shorthandCommand(args) : portableCommand(args),
|
|
29
|
+
});
|
|
30
|
+
const changes = [];
|
|
31
|
+
// stable runtime은 **프로젝트와도, profile 선택과도 무관하다.** 사람이 profile을
|
|
32
|
+
// 고르는 중이어도 이 설치는 안전한 준비이고, 그것 때문에 통째로 WAIT 하지 않는다
|
|
33
|
+
// (C-13 dependency-local progress와 같은 태도).
|
|
34
|
+
if (state.stableRuntime) {
|
|
35
|
+
const runtime = state.stableRuntime;
|
|
36
|
+
evidence.push(`runtime=${runtime.status}${runtime.installedVersion ? ` (${runtime.installedVersion})` : ''}`);
|
|
37
|
+
if (runtime.status !== 'CURRENT') {
|
|
38
|
+
changes.push({
|
|
39
|
+
target: 'runtime-install',
|
|
40
|
+
package: RUNTIME_PACKAGE,
|
|
41
|
+
version: runtime.expectedVersion,
|
|
42
|
+
strategy: 'npm-global',
|
|
43
|
+
from: runtime.status,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// Host는 붙어 있든 아니든 판정할 수 있다 — user-owned이고 프로젝트와 무관하다.
|
|
48
|
+
for (const host of state.host) {
|
|
49
|
+
if (host.status === 'INSTALLED_MODIFIED') {
|
|
50
|
+
evidence.push(`host:${host.id}=${host.status}`);
|
|
51
|
+
return {
|
|
52
|
+
status: 'user_action_required',
|
|
53
|
+
code: 'ASC_HOST_INSTALL_MODIFIED',
|
|
54
|
+
// 사람이 고친 것을 덮는 것은 사람이 정한다 — plan에 담아 몰래 적용하지 않는다.
|
|
55
|
+
// 다만 runtime 설치처럼 이 결정과 무관한 준비는 계획에 남는다.
|
|
56
|
+
changes,
|
|
57
|
+
requiresUserAction: true,
|
|
58
|
+
...actions(mode, evidence, [
|
|
59
|
+
{ type: 'force_host_install', ...command(['host', host.id, 'install', '--force']) },
|
|
60
|
+
]),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
if (host.status !== 'INSTALLED_CURRENT') {
|
|
64
|
+
evidence.push(`host:${host.id}=${host.status}`);
|
|
65
|
+
changes.push({ target: 'host-install', host: host.id, from: host.status });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (state.ascRoot) {
|
|
69
|
+
return finish(changes, evidence, state, mode, command);
|
|
70
|
+
}
|
|
71
|
+
// 아직 안 붙었다. 무엇으로 붙을지는 사람이 정한다.
|
|
72
|
+
const profile = state.requestedProfile ?? soleCandidate(state.profileCandidates);
|
|
73
|
+
if (!profile) {
|
|
74
|
+
evidence.push(`profile candidates=${state.profileCandidates.join(', ') || '(none)'}`);
|
|
75
|
+
return {
|
|
76
|
+
status: 'user_action_required',
|
|
77
|
+
code: 'ASC_PROFILE_SELECTION_REQUIRED',
|
|
78
|
+
changes,
|
|
79
|
+
requiresUserAction: true,
|
|
80
|
+
profiles: state.profileCandidates,
|
|
81
|
+
...actions(mode, evidence, [
|
|
82
|
+
{ type: 'select_profile', ...command(['setup', 'apply', '--profile', '<id>']) },
|
|
83
|
+
]),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
if (state.scope === 'project') {
|
|
87
|
+
// 저장소 안에 두는 것은 팀의 결정이고, 명시로만 표현된다. 여기서는 확인만 하고
|
|
88
|
+
// 그대로 계획에 담는다 — 사람이 이미 `--scope project` 라고 말했기 때문이다.
|
|
89
|
+
evidence.push('adoption=project (explicit)');
|
|
90
|
+
}
|
|
91
|
+
changes.push({ target: 'attach-workspace', scope: state.scope, profile });
|
|
92
|
+
evidence.push(`profile=${profile}${state.requestedProfile ? ' (given)' : ' (sole candidate)'}`);
|
|
93
|
+
return finish(changes, evidence, state, mode, command);
|
|
94
|
+
}
|
|
95
|
+
/** 후보가 하나뿐이어도 대신 고르지 않는다 — 여기서 돌려주는 것은 "고를 것이 없다"뿐이다. */
|
|
96
|
+
function soleCandidate(candidates) {
|
|
97
|
+
return candidates.length === 1 ? candidates[0] : undefined;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* 같은 것을 두 형태로 싣는다. `nextActions` 에는 **portable** 을 넣는다 —
|
|
101
|
+
* 기존 소비자가 그것을 실행 가능한 문자열로 읽고 있고, 설치 전에는 그것만 실제로 돈다.
|
|
102
|
+
*/
|
|
103
|
+
function actions(mode, evidence, list) {
|
|
104
|
+
return {
|
|
105
|
+
nextActions: list.map((action) => action.portable),
|
|
106
|
+
actions: list,
|
|
107
|
+
executionMode: mode,
|
|
108
|
+
evidence,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function finish(changes, evidence, state, mode, command) {
|
|
112
|
+
if (changes.length === 0) {
|
|
113
|
+
return {
|
|
114
|
+
status: 'already_configured',
|
|
115
|
+
changes,
|
|
116
|
+
requiresUserAction: false,
|
|
117
|
+
...actions(mode, evidence, state.ascRoot ? [{ type: 'proceed', ...command(['proceed']) }] : []),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
status: 'ready_to_apply',
|
|
122
|
+
changes,
|
|
123
|
+
requiresUserAction: false,
|
|
124
|
+
...actions(mode, evidence, [{ type: 'apply_setup', ...command(['setup', 'apply']) }]),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* plan에 적힌 것만 실행한다. **다시 판단하지 않는다** (C-14 불변식 ⑩).
|
|
129
|
+
*
|
|
130
|
+
* 여기서 상태를 다시 보고 마음을 바꾸면, 사람이 승인한 plan과 실제로 일어난 일이
|
|
131
|
+
* 달라진다. 그 순간 plan은 아무것도 보장하지 않는 문서가 된다.
|
|
132
|
+
*/
|
|
133
|
+
export async function applySetupPlan(plan, effects) {
|
|
134
|
+
const applied = [];
|
|
135
|
+
for (const change of plan.changes) {
|
|
136
|
+
switch (change.target) {
|
|
137
|
+
case 'runtime-install':
|
|
138
|
+
await effects.installRuntime(change);
|
|
139
|
+
break;
|
|
140
|
+
case 'attach-workspace':
|
|
141
|
+
await effects.attachWorkspace(change);
|
|
142
|
+
break;
|
|
143
|
+
case 'host-install':
|
|
144
|
+
await effects.installHost(change);
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
applied.push(change);
|
|
148
|
+
}
|
|
149
|
+
return { applied, changesApplied: applied.length > 0 };
|
|
150
|
+
}
|
|
151
|
+
/** 사람이 읽는 줄. 같은 plan에서 나온다 — agent가 보는 JSON과 다른 판단이 아니다. */
|
|
152
|
+
export function renderSetupPlan(plan) {
|
|
153
|
+
const lines = [`Status: ${plan.status}${plan.code ? ` (${plan.code})` : ''}`];
|
|
154
|
+
if (plan.changes.length === 0)
|
|
155
|
+
lines.push(' nothing to change');
|
|
156
|
+
for (const change of plan.changes) {
|
|
157
|
+
lines.push(changeLine(change));
|
|
158
|
+
}
|
|
159
|
+
if (plan.profiles && plan.profiles.length > 0)
|
|
160
|
+
lines.push(` choose one: ${plan.profiles.join(', ')}`);
|
|
161
|
+
else if (plan.profiles)
|
|
162
|
+
lines.push(' nothing to choose — this installation has no profile candidates');
|
|
163
|
+
// 사람에게는 짧은 형태를 보인다. agent가 실행하는 것은 `actions[].portable` 이다.
|
|
164
|
+
if (plan.actions.length > 0)
|
|
165
|
+
lines.push(` next: ${plan.actions.map((a) => a.display).join(' · ')}`);
|
|
166
|
+
return lines;
|
|
167
|
+
}
|
|
168
|
+
function changeLine(change) {
|
|
169
|
+
switch (change.target) {
|
|
170
|
+
case 'runtime-install':
|
|
171
|
+
return ` install ${change.package}@${change.version} globally (currently ${change.from})`;
|
|
172
|
+
case 'attach-workspace':
|
|
173
|
+
return ` attach: ${change.profile} · scope ${change.scope}`;
|
|
174
|
+
case 'host-install':
|
|
175
|
+
return ` converge host installation: ${change.host} (currently ${change.from})`;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/** 붙어 있는 상태 자체. 이게 READY가 아니면 gate 판정보다 이쪽이 먼저 답이다. */
|
|
2
|
+
export type AttachmentState = 'READY' | 'UNATTACHED' | 'BROKEN' | 'LOCK_DRIFT';
|
|
3
|
+
/**
|
|
4
|
+
* OPEN 열림 · BLOCKED 못 씀 · DEGRADED 돌긴 하는데 반쪽.
|
|
5
|
+
*
|
|
6
|
+
* 반쪽을 BLOCKED로 적으면 사람이 "안 되는구나" 하고 덮어 버린다. monitor scan이 돌면서도
|
|
7
|
+
* 나에게 온 것을 못 알아보는 상태가 정확히 그렇다 — 실행 자체가 막힌 것과 구분해야 한다.
|
|
8
|
+
*/
|
|
9
|
+
export type GateState = 'OPEN' | 'BLOCKED' | 'DEGRADED';
|
|
10
|
+
export type SetupGate = {
|
|
11
|
+
id: 'approval' | 'monitor' | 'external-write';
|
|
12
|
+
label: string;
|
|
13
|
+
state: GateState;
|
|
14
|
+
/** 무엇이 없어서 막혔는지. */
|
|
15
|
+
missing: string[];
|
|
16
|
+
/** 돌긴 하지만 알아야 할 것. */
|
|
17
|
+
warnings: string[];
|
|
18
|
+
/** 어떻게 여는지. 재고정이 필요한 것과 아닌 것을 구분해서 적는다. */
|
|
19
|
+
howTo: string[];
|
|
20
|
+
};
|
|
21
|
+
export type SetupStatus = {
|
|
22
|
+
attachment: AttachmentState;
|
|
23
|
+
/** 설정과 무관하게 지금 되는 것. */
|
|
24
|
+
ready: string[];
|
|
25
|
+
gates: SetupGate[];
|
|
26
|
+
};
|
|
27
|
+
export type SetupInput = {
|
|
28
|
+
attachment: AttachmentState;
|
|
29
|
+
/** 승인 권한자 매핑이 하나라도 있는가. */
|
|
30
|
+
hasApprovers: boolean;
|
|
31
|
+
/** override의 controller.identities가 채워졌는가. */
|
|
32
|
+
hasControllerIdentities: boolean;
|
|
33
|
+
/** override의 monitorIdentities가 채워졌는가. */
|
|
34
|
+
hasMonitorIdentities: boolean;
|
|
35
|
+
/** SCM 토큰을 찾았는가. 값은 받지 않는다 — 있는지만 안다. */
|
|
36
|
+
hasScmToken: boolean;
|
|
37
|
+
};
|
|
38
|
+
export declare function assessSetup(input: SetupInput): SetupStatus;
|
|
39
|
+
/** 사람이 읽는 형태. 상태가 먼저 오고, 되는 것, 막힌 것 순이다. */
|
|
40
|
+
export declare function renderSetup(status: SetupStatus): string;
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// Setup 상태 — 지금 무엇이 되고 무엇이 막혀 있는가 (B-21).
|
|
2
|
+
//
|
|
3
|
+
// 근거(B-16): attach 후 override.json·identities.json을 손으로 채워야 했는데, 무엇을 왜
|
|
4
|
+
// 채워야 하는지가 어디에도 없었다. 게다가 override는 고친 뒤 재고정하지 않으면 다음
|
|
5
|
+
// 명령이 통째로 멈추는데 그 사실을 drift가 난 뒤에야 알게 된다.
|
|
6
|
+
//
|
|
7
|
+
// 다시 재어 본 결과 이 문제는 생각보다 좁았다 — **막히는 것은 바깥 경로뿐이고**
|
|
8
|
+
// (외부 감시·승인 결정·외부 반영), 세션 발급부터 회수·마무리까지 로컬 루프는 설정 편집
|
|
9
|
+
// 0회로 전부 돈다. 그래서 이 모듈이 하는 일은 "설정을 받아 채우는 것"이 아니라
|
|
10
|
+
// **무엇이 열려 있고 무엇이 아직 안 열렸는지를 정직하게 말하는 것**이다.
|
|
11
|
+
//
|
|
12
|
+
// 판정만 한다. 파일도 env도 직접 읽지 않고 전부 주입받으며, 아무것도 고치지 않는다.
|
|
13
|
+
/**
|
|
14
|
+
* 설정을 하나도 안 채워도 되는 것들. B-16 이후 늘어난 표면(progress·preflight·closure)까지
|
|
15
|
+
* 포함한다 — "attach가 끝나야 아무것도 할 수 있다"는 오해를 이 목록이 직접 깬다.
|
|
16
|
+
*/
|
|
17
|
+
const ALWAYS_READY = [
|
|
18
|
+
'issue, run, pause, resume and finish sessions (asc session, asc proceed)',
|
|
19
|
+
'record and read progress (asc progress)',
|
|
20
|
+
'check output paths up front (asc preflight)',
|
|
21
|
+
'collect finished work and confirm closure (asc controller collect, asc closure)',
|
|
22
|
+
'read incoming requests (asc inbox list/show)',
|
|
23
|
+
'install and check the host (asc host)',
|
|
24
|
+
];
|
|
25
|
+
const RESOLVE_AGAIN = 'after editing, re-lock with `asc profile resolve --write` (otherwise the next command stops)';
|
|
26
|
+
export function assessSetup(input) {
|
|
27
|
+
return {
|
|
28
|
+
attachment: input.attachment,
|
|
29
|
+
ready: [...ALWAYS_READY],
|
|
30
|
+
gates: [approvalGate(input), monitorGate(input), externalWriteGate(input)],
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/** 승인 결정. identities.json은 lock digest에 없어 재고정이 필요 없다. */
|
|
34
|
+
function approvalGate(input) {
|
|
35
|
+
if (input.hasApprovers) {
|
|
36
|
+
return { id: 'approval', label: 'approval decisions', state: 'OPEN', missing: [], warnings: [], howTo: [] };
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
id: 'approval',
|
|
40
|
+
label: 'approval decisions',
|
|
41
|
+
state: 'BLOCKED',
|
|
42
|
+
missing: ['identities.json lists no approver'],
|
|
43
|
+
warnings: [],
|
|
44
|
+
// 여기에 재고정을 적지 않는다 — 필요 없는 절차를 시키면 다음부터 안내를 안 믿는다
|
|
45
|
+
howTo: ['open identities.json and add an approver in the $example form (no re-lock needed)'],
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* 외부 감시. 두 설정이 서로 다른 것을 막는다 —
|
|
50
|
+
* controller.identities가 없으면 실행 자체가 안 되고(단 --as로 우회 가능),
|
|
51
|
+
* monitorIdentities가 없으면 실행은 되는데 나에게 온 것을 못 알아본다.
|
|
52
|
+
*/
|
|
53
|
+
function monitorGate(input) {
|
|
54
|
+
const missing = [];
|
|
55
|
+
const warnings = [];
|
|
56
|
+
const howTo = [];
|
|
57
|
+
if (!input.hasControllerIdentities) {
|
|
58
|
+
missing.push('controller.identities in override.json is empty');
|
|
59
|
+
howTo.push(`list an approver in controller.identities, or pass \`--as <name>\` — ${RESOLVE_AGAIN}`);
|
|
60
|
+
}
|
|
61
|
+
if (!input.hasMonitorIdentities) {
|
|
62
|
+
warnings.push('monitorIdentities is empty, so mentions and assignments to you are not recognised');
|
|
63
|
+
howTo.push(`put your account name in monitorIdentities — ${RESOLVE_AGAIN}`);
|
|
64
|
+
}
|
|
65
|
+
if (!input.hasScmToken) {
|
|
66
|
+
missing.push('no SCM token found');
|
|
67
|
+
howTo.push('set ASC_GITHUB_TOKEN, or run `gh auth login`');
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
id: 'monitor',
|
|
71
|
+
label: 'external monitoring',
|
|
72
|
+
state: missing.length > 0 ? 'BLOCKED' : warnings.length > 0 ? 'DEGRADED' : 'OPEN',
|
|
73
|
+
missing,
|
|
74
|
+
warnings,
|
|
75
|
+
howTo,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/** 외부 반영. 승인된 Grant를 실제로 내보내려면 토큰과 발급 권한이 둘 다 있어야 한다. */
|
|
79
|
+
function externalWriteGate(input) {
|
|
80
|
+
const missing = [];
|
|
81
|
+
const howTo = [];
|
|
82
|
+
if (!input.hasApprovers) {
|
|
83
|
+
missing.push('no one can issue a grant (identities.json)');
|
|
84
|
+
howTo.push('add an approver to identities.json (no re-lock needed)');
|
|
85
|
+
}
|
|
86
|
+
if (!input.hasScmToken) {
|
|
87
|
+
missing.push('no SCM token found');
|
|
88
|
+
howTo.push('set ASC_GITHUB_TOKEN, or run `gh auth login`');
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
id: 'external-write',
|
|
92
|
+
label: 'external writes',
|
|
93
|
+
state: missing.length > 0 ? 'BLOCKED' : 'OPEN',
|
|
94
|
+
missing,
|
|
95
|
+
warnings: [],
|
|
96
|
+
howTo,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
/** 사람이 읽는 형태. 상태가 먼저 오고, 되는 것, 막힌 것 순이다. */
|
|
100
|
+
export function renderSetup(status) {
|
|
101
|
+
const lines = [];
|
|
102
|
+
switch (status.attachment) {
|
|
103
|
+
case 'UNATTACHED':
|
|
104
|
+
lines.push('Not attached yet — run `asc init --profile <id>` first.');
|
|
105
|
+
return lines.join('\n');
|
|
106
|
+
case 'BROKEN':
|
|
107
|
+
lines.push('Attachment is half-finished: a runtime exists but profile.lock does not.');
|
|
108
|
+
lines.push(' Re-attach with `asc init --profile <id>`, or lock it with `asc profile resolve --write`.');
|
|
109
|
+
return lines.join('\n');
|
|
110
|
+
case 'LOCK_DRIFT':
|
|
111
|
+
// 이 상태에서 gate를 나열하면 "설정이 덜 찼다"로 잘못 읽힌다. 원인을 먼저 말한다.
|
|
112
|
+
lines.push('Configuration differs from the lock, so commands stop — it was edited without re-locking.');
|
|
113
|
+
lines.push(' Re-lock with `asc profile resolve --write` and the state below applies again.');
|
|
114
|
+
lines.push('');
|
|
115
|
+
break;
|
|
116
|
+
case 'READY':
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
lines.push('Working now:');
|
|
120
|
+
for (const item of status.ready)
|
|
121
|
+
lines.push(` ${item}`);
|
|
122
|
+
const notOpen = status.gates.filter((g) => g.state !== 'OPEN');
|
|
123
|
+
const open = status.gates.filter((g) => g.state === 'OPEN');
|
|
124
|
+
if (open.length > 0) {
|
|
125
|
+
lines.push('', `Open outward paths: ${open.map((g) => g.label).join(' · ')}`);
|
|
126
|
+
}
|
|
127
|
+
if (notOpen.length > 0) {
|
|
128
|
+
lines.push('', 'Not open yet:');
|
|
129
|
+
for (const gate of notOpen) {
|
|
130
|
+
lines.push(` [${gate.state === 'DEGRADED' ? '~' : ' '}] ${gate.label}`);
|
|
131
|
+
for (const item of gate.missing)
|
|
132
|
+
lines.push(` ${item}`);
|
|
133
|
+
for (const item of gate.warnings)
|
|
134
|
+
lines.push(` ${item}`);
|
|
135
|
+
for (const item of gate.howTo)
|
|
136
|
+
lines.push(` → ${item}`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return lines.join('\n');
|
|
140
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* provider-neutral semantic operation (C-09 §1.1). 이 목록이 정본이다.
|
|
3
|
+
*
|
|
4
|
+
* 여기에 provider 이름을 넣지 않는다 — `scm.<제품>` 같은 값은 capability가 아니라
|
|
5
|
+
* adapter identity이며, 그것으로 갈라지기 시작하면 Core가 provider를 아는 셈이 된다.
|
|
6
|
+
*/
|
|
7
|
+
export declare const CAPABILITIES: readonly ["observe.delta", "inventory.enumerate", "context.resource", "context.thread", "context.change", "context.history", "canonical.read", "action.comment", "action.update", "presentation.digest", "presentation.priority", "approval.interactive", "identity.resolve"];
|
|
8
|
+
export type Capability = (typeof CAPABILITIES)[number];
|
|
9
|
+
/** describe() 산출물 — 호출도 네트워크도 없는 정적 선언 (C-09 §5). */
|
|
10
|
+
export type AdapterDescriptor = {
|
|
11
|
+
id: string;
|
|
12
|
+
version: string;
|
|
13
|
+
provides: readonly Capability[];
|
|
14
|
+
/**
|
|
15
|
+
* 자격이 필요하다는 **사실과 이름**까지만. 값은 여기에도, Profile에도 오지 않는다
|
|
16
|
+
* (OM §4.2·§4.5).
|
|
17
|
+
*/
|
|
18
|
+
requiresCredential?: readonly string[];
|
|
19
|
+
/** 실행 전제(외부 실행파일·프로세스 경계 등)를 사람이 읽을 문장으로. */
|
|
20
|
+
prerequisites?: readonly string[];
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* probe 결과. Boolean이 아닌 이유: "내가 설정을 안 한 것"과 "저쪽이 안 되는 것"을
|
|
24
|
+
* 합치면 사람이 무엇을 해야 할지 알 수 없다 (C-09 §5.1).
|
|
25
|
+
*/
|
|
26
|
+
export type ProbeState = 'AVAILABLE' | 'DEGRADED' | 'UNCONFIGURED' | 'UNAVAILABLE';
|
|
27
|
+
/** discover()가 찾아낸 후보 하나. 아직 쓸 수 있는지는 모른다. */
|
|
28
|
+
export type BindingCandidate = {
|
|
29
|
+
adapterId: string;
|
|
30
|
+
/** 어느 리소스인가. 문법은 adapter 소관이고 Core는 문자열로만 다룬다. */
|
|
31
|
+
resource: string;
|
|
32
|
+
/** 이 후보가 실제로 제공할 수 있다고 말하는 capability. describe의 부분집합이다. */
|
|
33
|
+
provides: readonly Capability[];
|
|
34
|
+
/** 어떻게 찾았는지 — 사람이 "이게 왜 후보인가"를 알 수 있어야 한다. */
|
|
35
|
+
discoveredBy?: string;
|
|
36
|
+
};
|
|
37
|
+
/** probe까지 마친 Binding. role은 사람이 정한다 — Core가 추론하지 않는다. */
|
|
38
|
+
export type ResolvedBinding = BindingCandidate & {
|
|
39
|
+
state: ProbeState;
|
|
40
|
+
/** DEGRADED·UNCONFIGURED·UNAVAILABLE의 이유. 상태만 주면 고칠 수가 없다. */
|
|
41
|
+
detail?: string;
|
|
42
|
+
/** 'code-primary' | 'work' | 'presentation' … Profile이 선언한 역할 이름. */
|
|
43
|
+
role?: string;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* adapter 자체의 상태. **binding과 별개 사실이다** — 도구는 쓸 수 있는데 이 프로젝트가
|
|
47
|
+
* 붙어 있지 않을 수 있고, 그 둘은 사람이 할 일이 다르다.
|
|
48
|
+
*/
|
|
49
|
+
export type AdapterRuntime = {
|
|
50
|
+
adapterId: string;
|
|
51
|
+
state: ProbeState;
|
|
52
|
+
detail?: string;
|
|
53
|
+
};
|
|
54
|
+
export type BindingPlan = {
|
|
55
|
+
bindings: readonly ResolvedBinding[];
|
|
56
|
+
/** 상태를 보고하는 adapter만 실린다. */
|
|
57
|
+
runtimes?: readonly AdapterRuntime[];
|
|
58
|
+
};
|
|
59
|
+
export type CapabilityResolution = {
|
|
60
|
+
capability: Capability;
|
|
61
|
+
kind: 'RESOLVED';
|
|
62
|
+
binding: ResolvedBinding;
|
|
63
|
+
}
|
|
64
|
+
/** 아무 binding도 제공하지 않는다 — 그 기능만 끈다 (조용한 통과가 아니다). */
|
|
65
|
+
| {
|
|
66
|
+
capability: Capability;
|
|
67
|
+
kind: 'UNAVAILABLE';
|
|
68
|
+
detail: string;
|
|
69
|
+
}
|
|
70
|
+
/** 둘 이상이 제공하는데 고를 수 없다. 임의 선택하지 않는다 (C-09 §4.2). */
|
|
71
|
+
| {
|
|
72
|
+
capability: Capability;
|
|
73
|
+
kind: 'AMBIGUOUS';
|
|
74
|
+
candidates: readonly ResolvedBinding[];
|
|
75
|
+
};
|
|
76
|
+
/** 한 작업이 필요로 하는 것. "provider가 뭐냐"가 아니라 이것으로 묻는다 (C-09 §4.1). */
|
|
77
|
+
export type CapabilityRequirement = {
|
|
78
|
+
capability: Capability;
|
|
79
|
+
/** 이 역할의 binding에서만 찾는다. 없으면 전체에서 찾는다. */
|
|
80
|
+
role?: string;
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* 요구 하나를 푼다.
|
|
84
|
+
*
|
|
85
|
+
* **쓸 수 있는 것만 후보다** — UNAVAILABLE·UNCONFIGURED는 애초에 세지 않는다.
|
|
86
|
+
* DEGRADED는 후보로 남긴다: 일부만 되는 것과 안 되는 것은 다르고, 어느 쪽을 쓸지는
|
|
87
|
+
* 호출자가 detail을 보고 정한다.
|
|
88
|
+
*/
|
|
89
|
+
export declare function resolveCapability(plan: BindingPlan, requirement: CapabilityRequirement): CapabilityResolution;
|
|
90
|
+
/** 여러 요구를 한 번에. 하나라도 못 풀면 그 사실이 결과에 남는다 — 부분 성공을 감추지 않는다. */
|
|
91
|
+
export declare function resolveAll(plan: BindingPlan, requirements: readonly CapabilityRequirement[]): CapabilityResolution[];
|
|
92
|
+
/** 지금 실제로 쓸 수 있는 capability 목록. setup·bootstrap 표면이 사람에게 보여준다. */
|
|
93
|
+
export declare function availableCapabilities(plan: BindingPlan): Capability[];
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// Adapter / Binding 어휘 — Core가 외부 시스템을 모른 채 조립을 판정하기 위한 타입 (C-09).
|
|
2
|
+
//
|
|
3
|
+
// 여기에는 provider 이름이 없다. adapter id는 **문자열로 실려 지나갈 뿐** Core가 그 값으로
|
|
4
|
+
// 행동을 바꾸지 않는다 (C-09 §6.1). 실제 adapter를 import하고 조립하는 곳은 Core 밖이다.
|
|
5
|
+
//
|
|
6
|
+
// 두 개념을 계속 구분한다 (C-09 §1):
|
|
7
|
+
// Interface(Port) 행동 계약 — 무엇을 어떻게 부르는가
|
|
8
|
+
// Capability availability descriptor — 그 행동을 지금 이 Binding에서 쓸 수 있는가
|
|
9
|
+
/**
|
|
10
|
+
* provider-neutral semantic operation (C-09 §1.1). 이 목록이 정본이다.
|
|
11
|
+
*
|
|
12
|
+
* 여기에 provider 이름을 넣지 않는다 — `scm.<제품>` 같은 값은 capability가 아니라
|
|
13
|
+
* adapter identity이며, 그것으로 갈라지기 시작하면 Core가 provider를 아는 셈이 된다.
|
|
14
|
+
*/
|
|
15
|
+
export const CAPABILITIES = [
|
|
16
|
+
'observe.delta',
|
|
17
|
+
'inventory.enumerate',
|
|
18
|
+
'context.resource',
|
|
19
|
+
'context.thread',
|
|
20
|
+
'context.change',
|
|
21
|
+
'context.history',
|
|
22
|
+
'canonical.read',
|
|
23
|
+
'action.comment',
|
|
24
|
+
'action.update',
|
|
25
|
+
'presentation.digest',
|
|
26
|
+
'presentation.priority',
|
|
27
|
+
'approval.interactive',
|
|
28
|
+
'identity.resolve',
|
|
29
|
+
];
|
|
30
|
+
/**
|
|
31
|
+
* 요구 하나를 푼다.
|
|
32
|
+
*
|
|
33
|
+
* **쓸 수 있는 것만 후보다** — UNAVAILABLE·UNCONFIGURED는 애초에 세지 않는다.
|
|
34
|
+
* DEGRADED는 후보로 남긴다: 일부만 되는 것과 안 되는 것은 다르고, 어느 쪽을 쓸지는
|
|
35
|
+
* 호출자가 detail을 보고 정한다.
|
|
36
|
+
*/
|
|
37
|
+
export function resolveCapability(plan, requirement) {
|
|
38
|
+
const usable = plan.bindings.filter((b) => (b.state === 'AVAILABLE' || b.state === 'DEGRADED') &&
|
|
39
|
+
b.provides.includes(requirement.capability) &&
|
|
40
|
+
(requirement.role === undefined || b.role === requirement.role));
|
|
41
|
+
if (usable.length === 0) {
|
|
42
|
+
// 후보가 아예 없는 것과, 있었는데 지금 못 쓰는 것을 구분해 말해 준다.
|
|
43
|
+
const blocked = plan.bindings.filter((b) => b.provides.includes(requirement.capability));
|
|
44
|
+
return {
|
|
45
|
+
capability: requirement.capability,
|
|
46
|
+
kind: 'UNAVAILABLE',
|
|
47
|
+
detail: blocked.length > 0
|
|
48
|
+
? `'${requirement.capability}' 를 제공하는 binding이 있으나 지금 쓸 수 없다 (${blocked
|
|
49
|
+
.map((b) => `${b.adapterId}: ${b.state}`)
|
|
50
|
+
.join(', ')})`
|
|
51
|
+
: `'${requirement.capability}' 를 제공하는 binding이 없다`,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
// 하나로 좁혀지지 않으면 고르지 않는다. 임의 선택은 그 선택을 사람이 영영 보지 못하게 한다.
|
|
55
|
+
if (usable.length > 1) {
|
|
56
|
+
return { capability: requirement.capability, kind: 'AMBIGUOUS', candidates: usable };
|
|
57
|
+
}
|
|
58
|
+
return { capability: requirement.capability, kind: 'RESOLVED', binding: usable[0] };
|
|
59
|
+
}
|
|
60
|
+
/** 여러 요구를 한 번에. 하나라도 못 풀면 그 사실이 결과에 남는다 — 부분 성공을 감추지 않는다. */
|
|
61
|
+
export function resolveAll(plan, requirements) {
|
|
62
|
+
return requirements.map((r) => resolveCapability(plan, r));
|
|
63
|
+
}
|
|
64
|
+
/** 지금 실제로 쓸 수 있는 capability 목록. setup·bootstrap 표면이 사람에게 보여준다. */
|
|
65
|
+
export function availableCapabilities(plan) {
|
|
66
|
+
const found = new Set();
|
|
67
|
+
for (const binding of plan.bindings) {
|
|
68
|
+
if (binding.state !== 'AVAILABLE' && binding.state !== 'DEGRADED')
|
|
69
|
+
continue;
|
|
70
|
+
for (const capability of binding.provides)
|
|
71
|
+
found.add(capability);
|
|
72
|
+
}
|
|
73
|
+
return [...found].sort();
|
|
74
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare const RUNTIME_PACKAGE = "@asc-agent/runtime";
|
|
2
|
+
export declare const BOOTSTRAP_PACKAGE = "@asc-agent/bootstrap";
|
|
3
|
+
/** runtime과 bootstrap은 초기 release에서 lockstep이다. */
|
|
4
|
+
export declare const RELEASE_VERSION = "0.1.0";
|
|
5
|
+
export declare const RUNTIME_SPEC = "@asc-agent/runtime@0.1.0";
|
|
6
|
+
export declare const BOOTSTRAP_SPEC = "@asc-agent/bootstrap@0.1.0";
|
|
7
|
+
/**
|
|
8
|
+
* 아직 설치되지 않은 machine에서 그대로 실행되는 형태 (C-14 §3.4).
|
|
9
|
+
*
|
|
10
|
+
* 사람이 읽는 `asc …` 는 설치 뒤에만 성립한다. agent에게 실행하라고 주는 것은
|
|
11
|
+
* 지금 상태에서 도는 것이어야 한다 (불변식 ⑯).
|
|
12
|
+
*/
|
|
13
|
+
export declare function portableCommand(args: readonly string[]): string;
|
|
14
|
+
/** 설치된 뒤 사람이 치는 형태. runtime이 CURRENT면 이것이 곧 portable이다. */
|
|
15
|
+
export declare function shorthandCommand(args: readonly string[]): string;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Release 상수 — 이 build가 어느 release에 속하는가 (C-14 §8).
|
|
2
|
+
//
|
|
3
|
+
// **exact pin on purpose.** `@latest` 도, major alias도 쓰지 않는다 — 테스트하지 않은
|
|
4
|
+
// runtime을 installer가 몰래 부르면 안 된다 (불변식 ⑧).
|
|
5
|
+
//
|
|
6
|
+
// 여기 값과 package.json 이 어긋나면 `npm run release:check` 가 잡는다. 소비자 환경에서
|
|
7
|
+
// package.json 을 런타임에 읽어 오는 방식은 쓰지 않는다 — 그 편의는 설치 형태마다 다르게
|
|
8
|
+
// 깨지고, drift는 어차피 사람이 release 직전에 한 번 확인하면 되는 문제다.
|
|
9
|
+
export const RUNTIME_PACKAGE = '@asc-agent/runtime';
|
|
10
|
+
export const BOOTSTRAP_PACKAGE = '@asc-agent/bootstrap';
|
|
11
|
+
/** runtime과 bootstrap은 초기 release에서 lockstep이다. */
|
|
12
|
+
export const RELEASE_VERSION = '0.1.0';
|
|
13
|
+
export const RUNTIME_SPEC = `${RUNTIME_PACKAGE}@${RELEASE_VERSION}`;
|
|
14
|
+
export const BOOTSTRAP_SPEC = `${BOOTSTRAP_PACKAGE}@${RELEASE_VERSION}`;
|
|
15
|
+
/**
|
|
16
|
+
* 아직 설치되지 않은 machine에서 그대로 실행되는 형태 (C-14 §3.4).
|
|
17
|
+
*
|
|
18
|
+
* 사람이 읽는 `asc …` 는 설치 뒤에만 성립한다. agent에게 실행하라고 주는 것은
|
|
19
|
+
* 지금 상태에서 도는 것이어야 한다 (불변식 ⑯).
|
|
20
|
+
*/
|
|
21
|
+
export function portableCommand(args) {
|
|
22
|
+
return `npx --yes ${BOOTSTRAP_SPEC} ${args.join(' ')}`;
|
|
23
|
+
}
|
|
24
|
+
/** 설치된 뒤 사람이 치는 형태. runtime이 CURRENT면 이것이 곧 portable이다. */
|
|
25
|
+
export function shorthandCommand(args) {
|
|
26
|
+
return `asc ${args.join(' ')}`;
|
|
27
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/** 명령 하나를 돌리고 결과를 돌려준다. 테스트는 가짜를, pilot은 진짜 npm을 넣는다. */
|
|
2
|
+
export type ProcessRunner = (command: string, args: readonly string[]) => Promise<{
|
|
3
|
+
ok: boolean;
|
|
4
|
+
stdout: string;
|
|
5
|
+
stderr: string;
|
|
6
|
+
}>;
|
|
7
|
+
export type StableInstallStatus = 'NOT_INSTALLED'
|
|
8
|
+
/** 기대한 exact version이 전역에 있고 실행물이 보인다. */
|
|
9
|
+
| 'CURRENT'
|
|
10
|
+
/** 설치는 돼 있는데 버전이 다르다. */
|
|
11
|
+
| 'VERSION_MISMATCH'
|
|
12
|
+
/** package는 있는데 실행물이 안 보이거나 조회가 깨졌다. */
|
|
13
|
+
| 'BROKEN';
|
|
14
|
+
export type StableInstallState = {
|
|
15
|
+
status: StableInstallStatus;
|
|
16
|
+
expectedVersion: string;
|
|
17
|
+
/** 전역에 실제로 있는 버전. 없으면 undefined. */
|
|
18
|
+
installedVersion?: string;
|
|
19
|
+
/** `asc` 가 이 프로세스에서 보이는가. */
|
|
20
|
+
executableVisible: boolean;
|
|
21
|
+
detail?: string;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* 전역 설치 상태를 읽는다. **읽기만 한다** (C-14 §6 detect).
|
|
25
|
+
*
|
|
26
|
+
* `npm ls -g --json` 은 전역 트리만 본다 — bootstrap이 들고 온 자기 의존성은 여기 안 나온다.
|
|
27
|
+
* 그것이 이 함수가 `npm ls` 를 쓰는 이유다.
|
|
28
|
+
*/
|
|
29
|
+
export declare function detectStableInstall(run: ProcessRunner, expectedVersion?: string): Promise<StableInstallState>;
|
|
30
|
+
export type InstallOutcome = {
|
|
31
|
+
ok: boolean;
|
|
32
|
+
detail?: string;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* 전역에 exact version으로 설치한다. **exact 말고는 쓰지 않는다** (불변식 ⑧).
|
|
36
|
+
*
|
|
37
|
+
* 여기서 조건을 다시 따지지 않는다 — 무엇을 설치할지는 plan이 이미 정했다 (불변식 ⑩).
|
|
38
|
+
*/
|
|
39
|
+
export declare function installStableRuntime(run: ProcessRunner, version?: string): Promise<InstallOutcome>;
|
|
40
|
+
/**
|
|
41
|
+
* 설치했다고 끝이 아니다 — exit 0은 "npm이 화내지 않았다"까지다 (C-14 §3.3).
|
|
42
|
+
*
|
|
43
|
+
* 버전이 맞는지, 그리고 **실행물이 실제로 보이는지**까지 본다. 안 보이면 성공으로
|
|
44
|
+
* 뭉개지 않고 새 터미널을 열라고 말한다.
|
|
45
|
+
*/
|
|
46
|
+
export declare function verifyStableInstall(run: ProcessRunner, expectedVersion?: string): Promise<{
|
|
47
|
+
ok: boolean;
|
|
48
|
+
state: StableInstallState;
|
|
49
|
+
remedy?: string;
|
|
50
|
+
}>;
|