@asc-agent/runtime 0.1.0 → 0.2.1
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 +4 -1
- package/dist/adapters/claude-code/skill.js +9 -1
- package/dist/cli/asc.d.ts +4 -5
- package/dist/cli/asc.js +378 -149
- package/dist/core/attach/adopt.d.ts +38 -0
- package/dist/core/attach/adopt.js +104 -0
- package/dist/core/attach/bootstrap.d.ts +13 -3
- package/dist/core/attach/bootstrap.js +14 -17
- package/dist/core/attach/setup-plan.d.ts +10 -1
- package/dist/core/attach/setup-plan.js +36 -8
- package/dist/core/attach/setup.d.ts +10 -0
- package/dist/core/attach/setup.js +10 -0
- package/dist/core/distribution/node-runtime.d.ts +44 -0
- package/dist/core/distribution/node-runtime.js +75 -0
- package/dist/core/distribution/release.d.ts +3 -3
- package/dist/core/distribution/release.js +1 -1
- package/dist/core/monitor/observation.d.ts +2 -2
- package/dist/core/resolver/load.d.ts +12 -1
- package/dist/core/resolver/load.js +23 -2
- package/dist/core/resolver/profile-source.d.ts +29 -0
- package/dist/core/resolver/profile-source.js +139 -0
- package/dist/core/workspace/identity.d.ts +2 -2
- package/dist/core/workspace/index-store.d.ts +10 -10
- package/dist/schemas/profile.d.ts +21 -21
- package/package.json +55 -55
package/dist/cli/asc.js
CHANGED
|
@@ -8,11 +8,12 @@
|
|
|
8
8
|
// 읽기 전용이다. 결정 제출은 사람의 명시적 의사표현을 받는 별도 경로로 나간다 (B-06).
|
|
9
9
|
import { execFile, spawnSync } from 'node:child_process';
|
|
10
10
|
import { parseArgs, promisify } from 'node:util';
|
|
11
|
-
import { existsSync, realpathSync } from 'node:fs';
|
|
11
|
+
import { existsSync, readdirSync, realpathSync } from 'node:fs';
|
|
12
12
|
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
13
13
|
import { fileURLToPath } from 'node:url';
|
|
14
14
|
import { homedir } from 'node:os';
|
|
15
|
-
import { dirname, join, resolve } from 'node:path';
|
|
15
|
+
import { basename, dirname, join, resolve } from 'node:path';
|
|
16
|
+
import { MINIMUM_NODE_MAJOR, checkNodeRuntime, } from "../core/distribution/node-runtime.js";
|
|
16
17
|
import { GitHubClient, discoverToken } from "../adapters/github/client.js";
|
|
17
18
|
import { GitHubChangeContext, GitHubInventory, GitHubResourceContext } from "../adapters/github/context.js";
|
|
18
19
|
import { GitHubEventSource } from "../adapters/github/event-source.js";
|
|
@@ -25,6 +26,7 @@ import { Executor } from "../core/execution/executor.js";
|
|
|
25
26
|
import { GrantService } from "../core/execution/grant.js";
|
|
26
27
|
import { DecisionKind } from "../core/model/entities.js";
|
|
27
28
|
import { discoverProjectRoot, excludeFromGit, identitiesTemplate, overrideTemplate, writeIfAbsent } from "../core/attach/init.js";
|
|
29
|
+
import { AdoptError, buildAdoptedProfile } from "../core/attach/adopt.js";
|
|
28
30
|
import { locatorsOf, lookupLocator, readIndex, register, writeIndex } from "../core/workspace/index-store.js";
|
|
29
31
|
import { adoptionLine, judgeAdoption, migrate } from "../core/workspace/migrate.js";
|
|
30
32
|
import { newWorkspaceId, normalizeRemote, recoverCandidates, recoverLines } from "../core/workspace/identity.js";
|
|
@@ -47,6 +49,7 @@ import { buildEventObservation } from "../composition/observe.js";
|
|
|
47
49
|
import { availableProfiles, planBootstrap, renderPlan } from "../core/attach/bootstrap.js";
|
|
48
50
|
import { detectStableInstall, installStableRuntime, verifyStableInstall, } from "../core/distribution/runtime-install.js";
|
|
49
51
|
import { readRuntimeSelection, remediationAction, remediationLines, resolveRuntimeTarget, runtimeSelectionLine, selectionPath, writeRuntimeSelection, } from "../core/distribution/runtime-select.js";
|
|
52
|
+
import { portableCommand, shorthandCommand } from "../core/distribution/release.js";
|
|
50
53
|
import { preflight } from "../core/operator/preflight.js";
|
|
51
54
|
import { lookupAuthority } from "../core/policy/ownership.js";
|
|
52
55
|
import { renderProgress } from "../core/operator/render.js";
|
|
@@ -66,137 +69,139 @@ import { FreezeLedger, freezeLines, judgeAction } from "../core/policy/remote-fr
|
|
|
66
69
|
import { SessionRuntime } from "../core/runtime/session.js";
|
|
67
70
|
import { Checkpoint, Handoff, SessionRole } from "../core/model/entities.js";
|
|
68
71
|
import { archiveLock, bootstrapGuard, buildLock, compareLock, loadLayers, resolveRuntime } from "../core/resolver/load.js";
|
|
72
|
+
import { ProfileSourceError } from "../core/resolver/profile-source.js";
|
|
69
73
|
import { renderAscMd, renderControllerMd } from "../core/resolver/render.js";
|
|
70
|
-
import { ProfileLock } from "../schemas/profile.js";
|
|
74
|
+
import { ProfileLock, ProjectProfile } from "../schemas/profile.js";
|
|
71
75
|
import { LocalOperator } from "../core/operator/local-operator.js";
|
|
72
76
|
import { loadIdentityMap } from "./identity-config.js";
|
|
73
|
-
const USAGE = `asc — Agent Session Control
|
|
74
|
-
|
|
75
|
-
asc proceed [--session <id>] [--goal <text>] [--json]
|
|
76
|
-
|
|
77
|
-
asc inbox list [--all] [--priority P0|P1|P2] [--json]
|
|
78
|
-
asc inbox show <REQUEST_ID> [--json]
|
|
79
|
-
asc inbox trace <REQUEST_ID> [--json] # how it got here — an exploratory trace
|
|
80
|
-
asc inbox digest [--flush] [--json] # batched view (P0 stays separate)
|
|
81
|
-
asc inbox latest [--priority P0|P1|P2] [--json]
|
|
82
|
-
asc inbox decide <REQUEST_ID> <approve|revise|defer|dismiss|queue> --as <actor>
|
|
83
|
-
[--revision <text>] [--expect <version>]
|
|
84
|
-
|
|
85
|
-
asc grant issue <REQUEST_ID> --action <key> --target <ref> --as <actor>
|
|
86
|
-
[--grant-id <id>] [--expires <iso>]
|
|
87
|
-
asc grant run <GRANT_ID> [--run-id <id>]
|
|
88
|
-
|
|
89
|
-
asc monitor scan [--backfill] [--as <controller>] # fast path
|
|
90
|
-
asc monitor reconcile [--as <controller>] # recover what was missed — re-list
|
|
91
|
-
asc monitor census [--as <controller>] # full reconcile + detect disappearances
|
|
92
|
-
asc monitor status [--json] # how far coverage has been confirmed
|
|
93
|
-
|
|
94
|
-
asc profile
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
asc runtime
|
|
100
|
-
asc runtime use
|
|
101
|
-
asc runtime use
|
|
102
|
-
asc
|
|
103
|
-
asc
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
asc escalate
|
|
107
|
-
asc
|
|
108
|
-
asc freeze
|
|
109
|
-
asc freeze
|
|
110
|
-
asc freeze
|
|
111
|
-
asc
|
|
112
|
-
asc
|
|
113
|
-
asc workspace
|
|
114
|
-
asc
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
asc setup
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
asc setup --
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
asc session
|
|
130
|
-
asc session
|
|
131
|
-
asc session
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
[--
|
|
135
|
-
[--
|
|
136
|
-
[--
|
|
137
|
-
|
|
138
|
-
asc session
|
|
139
|
-
asc session
|
|
140
|
-
asc session
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
asc closure
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
[--
|
|
154
|
-
[--
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
asc progress
|
|
161
|
-
|
|
162
|
-
[--
|
|
163
|
-
[--
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
asc host claude
|
|
167
|
-
asc host claude
|
|
168
|
-
asc host claude
|
|
169
|
-
asc host claude
|
|
170
|
-
asc host claude
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
--
|
|
175
|
-
--
|
|
176
|
-
--
|
|
177
|
-
--
|
|
178
|
-
--
|
|
179
|
-
--
|
|
180
|
-
--
|
|
181
|
-
--
|
|
182
|
-
--
|
|
183
|
-
--
|
|
184
|
-
--
|
|
185
|
-
--
|
|
186
|
-
--
|
|
187
|
-
--
|
|
188
|
-
--
|
|
189
|
-
--
|
|
190
|
-
--
|
|
191
|
-
--
|
|
192
|
-
--
|
|
193
|
-
--
|
|
194
|
-
--
|
|
195
|
-
--
|
|
196
|
-
--
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
77
|
+
const USAGE = `asc — Agent Session Control
|
|
78
|
+
|
|
79
|
+
asc proceed [--session <id>] [--goal <text>] [--json]
|
|
80
|
+
|
|
81
|
+
asc inbox list [--all] [--priority P0|P1|P2] [--json]
|
|
82
|
+
asc inbox show <REQUEST_ID> [--json]
|
|
83
|
+
asc inbox trace <REQUEST_ID> [--json] # how it got here — an exploratory trace
|
|
84
|
+
asc inbox digest [--flush] [--json] # batched view (P0 stays separate)
|
|
85
|
+
asc inbox latest [--priority P0|P1|P2] [--json]
|
|
86
|
+
asc inbox decide <REQUEST_ID> <approve|revise|defer|dismiss|queue> --as <actor>
|
|
87
|
+
[--revision <text>] [--expect <version>]
|
|
88
|
+
|
|
89
|
+
asc grant issue <REQUEST_ID> --action <key> --target <ref> --as <actor>
|
|
90
|
+
[--grant-id <id>] [--expires <iso>]
|
|
91
|
+
asc grant run <GRANT_ID> [--run-id <id>]
|
|
92
|
+
|
|
93
|
+
asc monitor scan [--backfill] [--as <controller>] # fast path
|
|
94
|
+
asc monitor reconcile [--as <controller>] # recover what was missed — re-list
|
|
95
|
+
asc monitor census [--as <controller>] # full reconcile + detect disappearances
|
|
96
|
+
asc monitor status [--json] # how far coverage has been confirmed
|
|
97
|
+
|
|
98
|
+
asc profile adopt [--id <name>] [--json] # make a profile for this repository
|
|
99
|
+
asc profile resolve --profile <id> [--preset <id>] [--install <path>] [--write]
|
|
100
|
+
|
|
101
|
+
asc runtime start [--interval-min <n>] [--delta-min <n>] [--reconcile-min <n>]
|
|
102
|
+
[--census-min <n>] [--digest-min <n>]
|
|
103
|
+
asc runtime tick
|
|
104
|
+
asc runtime status [--json] # which build is in use (C-14 §4)
|
|
105
|
+
asc runtime use package
|
|
106
|
+
asc runtime use development <checkout> # run a built checkout instead
|
|
107
|
+
asc front [status] [--json]
|
|
108
|
+
asc escalate open <S-ID> --predicate <p>... --question <t> --blocked <node>...
|
|
109
|
+
--evidence <ref>... [--blocked-scope <path>...] [--previous <ESC-ID> --why <t>]
|
|
110
|
+
asc escalate list
|
|
111
|
+
asc escalate resolve <ESC-ID>
|
|
112
|
+
asc freeze [status]
|
|
113
|
+
asc freeze on --reason <text> [--offline]
|
|
114
|
+
asc freeze defer --id <id> --intent <text> [--evidence <ref>]
|
|
115
|
+
asc freeze release --id <id>
|
|
116
|
+
asc thaw
|
|
117
|
+
asc workspace list
|
|
118
|
+
asc workspace migrate [--force]
|
|
119
|
+
asc init [--profile <id>] [--preset <id>] [--install <path>]
|
|
120
|
+
[--scope local|project] [--workspace <W-id>]
|
|
121
|
+
# without --profile: report what was detected, then stop
|
|
122
|
+
|
|
123
|
+
asc setup status [--json]
|
|
124
|
+
asc setup plan [--profile <id>] [--scope local|project] [--json]
|
|
125
|
+
# says what it would change — changes nothing
|
|
126
|
+
asc setup apply [--profile <id>] [--scope local|project] [--json]
|
|
127
|
+
asc setup apply --json # non-interactive apply. stdout is a single JSON document
|
|
128
|
+
|
|
129
|
+
asc session issue <ID> --role <role> --goal <text> [--block <id>]
|
|
130
|
+
[--parent <S-ID>] [--issued-by <principal>]
|
|
131
|
+
asc session pause <S-ID> --position <t> --next <t> [--physical <id>]
|
|
132
|
+
[--judgment <t>] [--blocker <t>] [--risk <t>] [--evidence <ref>]
|
|
133
|
+
asc session validate <target S-ID> --validator <validator S-ID> --result PASS|FAIL [--finding <t>]
|
|
134
|
+
asc session audit <S-ID>
|
|
135
|
+
asc session report <S-ID> [--json]
|
|
136
|
+
asc session decision <S-ID> --class <c> --selected <t> --why <t>... --evidence <ref>...
|
|
137
|
+
[--alternative <t>...] [--ownership <scope>...] [--verification <t>...]
|
|
138
|
+
[--boundary <glob>...] [--exception <item>...]
|
|
139
|
+
[--criteria <text>...] [--owner <role>]
|
|
140
|
+
[--domain <decision-domain>...] [--authority <domain>=<role>...]
|
|
141
|
+
[--dependency <text>...]
|
|
142
|
+
asc session start <ID>
|
|
143
|
+
asc session pause <ID> --position <text> --next <text> [--done <task>...]
|
|
144
|
+
asc session resume <ID>
|
|
145
|
+
asc session done <ID> --verified <text> --next <text> [--done <task>...]
|
|
146
|
+
[--changed <path>...] [--unresolved <text>...]
|
|
147
|
+
asc session list
|
|
148
|
+
|
|
149
|
+
asc controller collect
|
|
150
|
+
|
|
151
|
+
asc closure list [<S-ID>]
|
|
152
|
+
asc closure confirm <S-ID> --item <id>...
|
|
153
|
+
|
|
154
|
+
asc preflight --path <p>... (--role <r> | --session <S-ID>) [--json]
|
|
155
|
+
|
|
156
|
+
asc query open <X-ID> --session <S-ID> --domain <decision-domain>
|
|
157
|
+
--question <text> [--context <text>] [--default <text>]
|
|
158
|
+
[--blocking <text>] [--expect-response DECIDE|ANSWER]
|
|
159
|
+
[--in-reply-to <X-ID>]
|
|
160
|
+
asc query answer <X-ID> --kind DECIDE|ANSWER|ESCALATE --by <role> --body <text>
|
|
161
|
+
[--to <authority>]
|
|
162
|
+
asc query list [--json]
|
|
163
|
+
|
|
164
|
+
asc progress show [<S-ID>]
|
|
165
|
+
asc progress report <S-ID> --physical <id> --phase <text>
|
|
166
|
+
[--milestone <text>...] [--next <text>] [--unresolved <text>...]
|
|
167
|
+
[--decision none|later|now] [--decision-ref <text>]
|
|
168
|
+
[--verifier none|running|pass|fail] [--verifier-detail <text>] [--terminal]
|
|
169
|
+
|
|
170
|
+
asc host claude install [--force] # --force: overwrite ASC files a person has edited
|
|
171
|
+
asc host claude uninstall|probe [--report <cap>=<bool>...]
|
|
172
|
+
asc host claude guard
|
|
173
|
+
asc host claude bind <S-ID> --physical <id> [--principal <p>] [--worker <id>] [--kind <k>] [--force]
|
|
174
|
+
asc host claude release <S-ID> --physical <id>
|
|
175
|
+
asc host claude contract <S-ID>
|
|
176
|
+
|
|
177
|
+
Options
|
|
178
|
+
--root <path> runtime directory (otherwise: registered workspace, then repo-local .asc)
|
|
179
|
+
--json machine-readable output
|
|
180
|
+
--as <actor> who is deciding. Must be mapped as an approver
|
|
181
|
+
--revision what was changed, when approving with revisions
|
|
182
|
+
--expect the version you read. Rejected if it changed since
|
|
183
|
+
--action action key to emit (e.g. github.issue_comment.create)
|
|
184
|
+
--target target reference (e.g. owner/repo#19)
|
|
185
|
+
--backfill sweep history on the first run (default: from now on)
|
|
186
|
+
--profile Project Profile id
|
|
187
|
+
--preset Operational Preset id
|
|
188
|
+
--install ASC installation path (default: where this CLI lives)
|
|
189
|
+
--write actually write the artefacts (default: preview)
|
|
190
|
+
--role planner|researcher|implementer|verifier
|
|
191
|
+
--goal the single goal of this session
|
|
192
|
+
--boundary write scope (must be narrower than the Profile's)
|
|
193
|
+
--exception SOFT DENY item allowed for this session only
|
|
194
|
+
--criteria a verifiable done-criterion (repeatable)
|
|
195
|
+
--path output path to check (preflight, repeatable)
|
|
196
|
+
--item confirmed closure item id (closure confirm, repeatable)
|
|
197
|
+
--phase one line on what is happening right now (progress report)
|
|
198
|
+
--milestone a meaningfully finished chunk (repeatable)
|
|
199
|
+
--decision does a person need to decide: none|later|now
|
|
200
|
+
--verifier independent verification state: none|running|pass|fail
|
|
201
|
+
--terminal final report — stays as the closing screen after collect
|
|
202
|
+
|
|
203
|
+
decide assumes a person is operating it, and only checks that the name given with --as
|
|
204
|
+
is registered as an approver. Approval is not permission to publish: anything reaching an
|
|
200
205
|
external system goes out through a separate Execution Grant.`;
|
|
201
206
|
/**
|
|
202
207
|
* 지금 여기가 어느 ASC runtime인가. **모든 명령이 같은 문을 지난다** (C-11 §3, B-45).
|
|
@@ -293,6 +298,12 @@ const DECISION_ERROR = {
|
|
|
293
298
|
ALREADY_DECIDED: '이미 결정된 요청이다.',
|
|
294
299
|
STALE: '읽은 뒤 요청이 바뀌었다. 다시 확인하고 결정하라.',
|
|
295
300
|
};
|
|
301
|
+
/**
|
|
302
|
+
* bootstrap이 자기 USAGE에 적을 값. **거기서 손으로 적지 않게 하려고 내보낸다** —
|
|
303
|
+
* 두 패키지가 각자 버전 문자열을 들면 릴리스마다 한쪽이 뒤처지고, 그 지연은 곧
|
|
304
|
+
* 사용자가 실행하는 명령이 된다 (0.2.0 회차의 skill.ts가 그랬다).
|
|
305
|
+
*/
|
|
306
|
+
export { BOOTSTRAP_SPEC } from "../core/distribution/release.js";
|
|
296
307
|
/**
|
|
297
308
|
* CLI 한 번의 실행. **다른 진입도 이 함수를 부른다** (C-14 불변식 ①).
|
|
298
309
|
*
|
|
@@ -300,6 +311,82 @@ const DECISION_ERROR = {
|
|
|
300
311
|
* 진입이 둘이어도 판단은 하나다. 그래서 export이고, 그래서 아래 자동 실행은 이 파일이
|
|
301
312
|
* 진짜 진입점일 때만 돈다.
|
|
302
313
|
*/
|
|
314
|
+
/**
|
|
315
|
+
* 설정을 읽다 실패한 것을 **사람이 읽을 문장**으로 바꾼다.
|
|
316
|
+
*
|
|
317
|
+
* 여기가 없으면 Profile 하나가 잘못됐을 때 사용자가 보는 것은 Node의 stack dump다 —
|
|
318
|
+
* 내부 파일 이름과 프레임이 줄줄이 나오고, 정작 "무엇을 고쳐야 하는지"는 없다.
|
|
319
|
+
* 독립 검증이 다섯 갈래(충돌·깨진 JSON·디렉터리가 아닌 것·EISDIR·긴 id)에서 같은 모양을
|
|
320
|
+
* 관측했다. 예상 못 한 오류는 그대로 던진다 — 삼키면 그게 더 나쁘다.
|
|
321
|
+
*/
|
|
322
|
+
function explainConfigError(error) {
|
|
323
|
+
if (error instanceof ProfileSourceError)
|
|
324
|
+
return error.message;
|
|
325
|
+
const failure = error;
|
|
326
|
+
const path = failure?.path ? ` (${failure.path})` : '';
|
|
327
|
+
switch (failure?.code) {
|
|
328
|
+
case 'ENOENT':
|
|
329
|
+
return `That profile is not there${path}. \`asc setup status\` lists what is.`;
|
|
330
|
+
case 'EISDIR':
|
|
331
|
+
return `A profile has to be a file, and that is a directory${path}.`;
|
|
332
|
+
case 'EACCES':
|
|
333
|
+
case 'EPERM':
|
|
334
|
+
return `No permission to read that profile${path}.`;
|
|
335
|
+
default:
|
|
336
|
+
break;
|
|
337
|
+
}
|
|
338
|
+
if (error instanceof SyntaxError)
|
|
339
|
+
return `That profile is not valid JSON — ${error.message}`;
|
|
340
|
+
return null;
|
|
341
|
+
}
|
|
342
|
+
/** 실제 파일시스템·프로세스를 물린다. Core는 이 중 아무것도 직접 하지 않는다. */
|
|
343
|
+
function nodeRuntimeDeps() {
|
|
344
|
+
return {
|
|
345
|
+
version: process.version,
|
|
346
|
+
exists: (path) => existsSync(path),
|
|
347
|
+
list: (path) => {
|
|
348
|
+
try {
|
|
349
|
+
return readdirSync(path);
|
|
350
|
+
}
|
|
351
|
+
catch {
|
|
352
|
+
// 없는 디렉터리는 "후보 없음"이다. 이 machine에 그 배치가 없을 뿐이다.
|
|
353
|
+
return [];
|
|
354
|
+
}
|
|
355
|
+
},
|
|
356
|
+
run: nodeProcessRunner,
|
|
357
|
+
home: homedir(),
|
|
358
|
+
join,
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* 못 돌린다는 사실과, 이 machine에서 실제로 쓸 수 있는 것을 함께 준다.
|
|
363
|
+
*
|
|
364
|
+
* 후보가 있으면 **같은 canonical 명령을 그 Node로 돌리는 형태**를 낸다 — 이것은 per-invocation
|
|
365
|
+
* 환경변수이지 PATH·profile 수정이 아니다 (불변식 ⑰). 이 형태마저 host가 실행을 거부하면
|
|
366
|
+
* 그때는 ASC의 문제가 아니라 host 경계이며, AGENTS.md가 그 자리를 정의한다.
|
|
367
|
+
*/
|
|
368
|
+
function reportNodeRuntime(check, asJson) {
|
|
369
|
+
const actions = check.candidates.map((candidate) => ({
|
|
370
|
+
type: 'use_node_runtime',
|
|
371
|
+
display: `PATH="${dirname(candidate.path)}:$PATH" ${shorthandCommand(['setup', 'apply', '--json'])}`,
|
|
372
|
+
portable: `PATH="${dirname(candidate.path)}:$PATH" ${portableCommand(['setup', 'apply', '--json'])}`,
|
|
373
|
+
node: candidate,
|
|
374
|
+
}));
|
|
375
|
+
if (asJson) {
|
|
376
|
+
console.log(JSON.stringify({
|
|
377
|
+
code: check.code,
|
|
378
|
+
detail: check.detail,
|
|
379
|
+
runtime: { node: process.execPath, version: check.version, required: `>=${MINIMUM_NODE_MAJOR}` },
|
|
380
|
+
candidates: check.candidates,
|
|
381
|
+
nextActions: actions.map((action) => action.portable),
|
|
382
|
+
actions,
|
|
383
|
+
}, null, 2));
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
console.error(check.detail);
|
|
387
|
+
for (const action of actions)
|
|
388
|
+
console.error(` ${action.node.version} at ${action.node.path}\n ${action.display}`);
|
|
389
|
+
}
|
|
303
390
|
export async function runAscCommand(argv, entry = 'runtime') {
|
|
304
391
|
const { values, positionals } = parseArgs({
|
|
305
392
|
args: argv,
|
|
@@ -405,6 +492,17 @@ export async function runAscCommand(argv, entry = 'runtime') {
|
|
|
405
492
|
console.log(USAGE);
|
|
406
493
|
return 0;
|
|
407
494
|
}
|
|
495
|
+
// **지원 하한을 먼저 답한다** (C-14 §3). `engines` 는 npm에게 하는 말이라 기본값에서
|
|
496
|
+
// 경고로만 나가고, 그러면 "경고 뒤에 그래도 돌아감"이 된다 — 사용자는 자기가 지원
|
|
497
|
+
// 범위 안인지 끝내 모른다. 여기서 한 번, 결정적으로 답한다.
|
|
498
|
+
//
|
|
499
|
+
// 이 자리인 이유: 설치된 `asc` 와 bootstrap이 **같은 문으로 들어온다**. bootstrap에
|
|
500
|
+
// 두면 그쪽에 정책이 생기고(C-14 불변식 ⑦), 그러면 두 진입의 답이 갈릴 수 있다.
|
|
501
|
+
const runnable = await checkNodeRuntime(nodeRuntimeDeps());
|
|
502
|
+
if (!runnable.ok) {
|
|
503
|
+
reportNodeRuntime(runnable, Boolean(values.json) || Boolean(values.agent));
|
|
504
|
+
return 1;
|
|
505
|
+
}
|
|
408
506
|
// 선택된 build로 넘길 것이 있으면 여기서 넘긴다. **선택 자체를 다루는 명령은 넘기지
|
|
409
507
|
// 않는다** — 잘못 가리키는 선택을 고치거나 들여다보는 명령이 그 선택 때문에 못 돌면
|
|
410
508
|
// 사람이 갇힌다.
|
|
@@ -427,6 +525,10 @@ export async function runAscCommand(argv, entry = 'runtime') {
|
|
|
427
525
|
// 그러면 "아직 안 붙었다"를 확인하려고 부른 명령이 안 붙었다는 이유로 죽는다.
|
|
428
526
|
if (group === 'setup')
|
|
429
527
|
return runSetup(command, values, entry);
|
|
528
|
+
// adopt는 **붙기 전의 명령이다.** 붙을 Profile을 만드는 것이 일이므로 attach를 요구하면
|
|
529
|
+
// 순서가 뒤집힌다. 나머지 profile 명령은 아래 attach 경로에 그대로 남는다.
|
|
530
|
+
if (group === 'profile' && command === 'adopt')
|
|
531
|
+
return runProfileAdopt(values, entry);
|
|
430
532
|
if (!['inbox', 'grant', 'monitor', 'runtime', 'front', 'freeze', 'thaw', 'escalate', 'profile', 'session', 'controller', 'proceed', 'progress', 'preflight', 'closure', 'query'].includes(group)) {
|
|
431
533
|
console.error(`Unknown command: ${group}\n\n${USAGE}`);
|
|
432
534
|
return 2;
|
|
@@ -616,7 +718,7 @@ async function attachLocalWorkspace(projectRoot, git, declaredWorkspace) {
|
|
|
616
718
|
return existing.root;
|
|
617
719
|
}
|
|
618
720
|
const remotes = git ? await gitRemotes(projectRoot) : [];
|
|
619
|
-
const aliases = remotes
|
|
721
|
+
const aliases = remoteAliases(remotes);
|
|
620
722
|
// 사람이 "이건 그 프로젝트다"라고 말한 경우 — 이어붙인다. 추론이 아니라 선언이다.
|
|
621
723
|
if (declaredWorkspace) {
|
|
622
724
|
const known = index.workspaces[declaredWorkspace];
|
|
@@ -635,7 +737,7 @@ async function attachLocalWorkspace(projectRoot, git, declaredWorkspace) {
|
|
|
635
737
|
console.log(`Registered this location under workspace ${known.workspaceId} — runtime lives at ${root}`);
|
|
636
738
|
return root;
|
|
637
739
|
}
|
|
638
|
-
const hits = recoverCandidates(Object.values(index.workspaces),
|
|
740
|
+
const hits = recoverCandidates(Object.values(index.workspaces), aliases);
|
|
639
741
|
if (hits.length > 0) {
|
|
640
742
|
// 붙일지는 사람이 정한다. 여기서 이어붙이면 남의 workspace를 조용히 가져올 수 있다.
|
|
641
743
|
for (const line of recoverLines(hits))
|
|
@@ -658,20 +760,33 @@ async function attachLocalWorkspace(projectRoot, git, declaredWorkspace) {
|
|
|
658
760
|
console.log(`Recognisable as: ${aliases.join(', ')}`);
|
|
659
761
|
return root;
|
|
660
762
|
}
|
|
661
|
-
/**
|
|
763
|
+
/**
|
|
764
|
+
* 모든 remote를 evidence로 모은다. origin을 primary로 단정하지 않는다 (C-11 불변식 ④).
|
|
765
|
+
*
|
|
766
|
+
* 이름을 함께 든다 — identity alias는 이름이 필요 없지만, `profile adopt` 는 어느 remote가
|
|
767
|
+
* 이 프로젝트를 대표하는지 골라야 하고 `git remote -v` 의 출력 순서는 알파벳순이라
|
|
768
|
+
* "첫 줄이 origin"이 아니다.
|
|
769
|
+
*/
|
|
662
770
|
async function gitRemotes(projectRoot) {
|
|
663
771
|
try {
|
|
664
772
|
const { stdout } = await execFileAsync('git', ['-C', projectRoot, 'remote', '-v']);
|
|
665
|
-
const
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
773
|
+
const seen = new Map();
|
|
774
|
+
for (const line of stdout.split(/\r?\n/)) {
|
|
775
|
+
const [name, url] = line.split(/\s+/);
|
|
776
|
+
if (!name || !url || seen.has(`${name}${url}`))
|
|
777
|
+
continue;
|
|
778
|
+
seen.set(`${name}${url}`, { name, url });
|
|
779
|
+
}
|
|
780
|
+
return [...seen.values()];
|
|
670
781
|
}
|
|
671
782
|
catch {
|
|
672
783
|
return [];
|
|
673
784
|
}
|
|
674
785
|
}
|
|
786
|
+
/** identity alias는 이름을 쓰지 않는다 — URL만 정규화한다. */
|
|
787
|
+
const remoteAliases = (remotes) => [...new Set(remotes.map((remote) => remote.url))]
|
|
788
|
+
.map(normalizeRemote)
|
|
789
|
+
.filter((alias) => alias !== null);
|
|
675
790
|
/**
|
|
676
791
|
* 저장소 안에 있던 `.asc/` 를 사용자 소유 공간으로 옮긴다 (C-11 §6).
|
|
677
792
|
*
|
|
@@ -679,8 +794,8 @@ async function gitRemotes(projectRoot) {
|
|
|
679
794
|
*/
|
|
680
795
|
async function runWorkspace(command, values) {
|
|
681
796
|
if (command !== 'migrate' && command !== 'list') {
|
|
682
|
-
console.error(`Unknown workspace command: ${command ?? '(none)'}
|
|
683
|
-
|
|
797
|
+
console.error(`Unknown workspace command: ${command ?? '(none)'}
|
|
798
|
+
|
|
684
799
|
${USAGE}`);
|
|
685
800
|
return 2;
|
|
686
801
|
}
|
|
@@ -726,7 +841,7 @@ ${USAGE}`);
|
|
|
726
841
|
workspaceId,
|
|
727
842
|
root: target,
|
|
728
843
|
locator: { path: projectRoot, platform: process.platform, observedAt: new Date().toISOString() },
|
|
729
|
-
aliases: remotes
|
|
844
|
+
aliases: remoteAliases(remotes),
|
|
730
845
|
now: new Date().toISOString(),
|
|
731
846
|
}));
|
|
732
847
|
console.log(`Copied and verified ${outcome.plan.entries} entries into ${target} (workspace ${workspaceId}).`);
|
|
@@ -773,6 +888,7 @@ async function runInit(values) {
|
|
|
773
888
|
const plan = await planBootstrap({
|
|
774
889
|
cwd: process.cwd(),
|
|
775
890
|
installRoot: installRoot(),
|
|
891
|
+
externalProfileRoot: externalProfileRoot(),
|
|
776
892
|
...(attachedRoot ? { ascRoot: attachedRoot } : {}),
|
|
777
893
|
hosts: [{ id: 'claude', installed: await verifyInstalled(defaultPaths()) }],
|
|
778
894
|
bindings,
|
|
@@ -788,6 +904,24 @@ async function runInit(values) {
|
|
|
788
904
|
}
|
|
789
905
|
const { root: projectRoot, git } = await discoverProjectRoot(process.cwd());
|
|
790
906
|
console.log(`Project: ${projectRoot}${git ? '' : ' (not a git repository)'}`);
|
|
907
|
+
// **읽을 수 있는 Profile인지 먼저 본다.** 아래부터는 `.git/info/exclude` 를 고치고
|
|
908
|
+
// 템플릿을 만드는 등 세상을 바꾸는 일이고, 그 뒤에 Profile이 잘못된 것을 알면 반쯤 만든
|
|
909
|
+
// `.asc/` 가 남는다 — 독립 검증이 실제로 그 상태를 만들었다. 여기서 멈추면 아무것도 남지 않는다.
|
|
910
|
+
try {
|
|
911
|
+
await loadLayers({
|
|
912
|
+
installRoot: installRoot(),
|
|
913
|
+
externalProfileRoot: externalProfileRoot(),
|
|
914
|
+
profileId: values.profile,
|
|
915
|
+
});
|
|
916
|
+
}
|
|
917
|
+
catch (error) {
|
|
918
|
+
const explained = explainConfigError(error);
|
|
919
|
+
if (explained === null)
|
|
920
|
+
throw error;
|
|
921
|
+
console.error(explained);
|
|
922
|
+
console.error('Nothing was changed.');
|
|
923
|
+
return 2;
|
|
924
|
+
}
|
|
791
925
|
const ascRoot = scope === 'project'
|
|
792
926
|
? join(projectRoot, '.asc')
|
|
793
927
|
: await attachLocalWorkspace(projectRoot, git, values.workspace);
|
|
@@ -824,6 +958,7 @@ async function attachedRuntime(ascRoot) {
|
|
|
824
958
|
const outcome = await bootstrapGuard({
|
|
825
959
|
ascRoot,
|
|
826
960
|
installRoot: installRoot(),
|
|
961
|
+
externalProfileRoot: externalProfileRoot(),
|
|
827
962
|
capabilities: CAPABILITIES,
|
|
828
963
|
adapters: ADAPTER_VERSIONS,
|
|
829
964
|
ascVersion: ASC_VERSION,
|
|
@@ -851,7 +986,7 @@ function declaredPolicies(resolved) {
|
|
|
851
986
|
}
|
|
852
987
|
return declared;
|
|
853
988
|
}
|
|
854
|
-
const ASC_VERSION = '0.1
|
|
989
|
+
const ASC_VERSION = '0.2.1';
|
|
855
990
|
const CAPABILITIES = ['scm.github', 'state.markdown', 'approval.local'];
|
|
856
991
|
const ADAPTER_VERSIONS = { 'scm.github': ASC_VERSION, 'state.markdown': ASC_VERSION };
|
|
857
992
|
/**
|
|
@@ -981,11 +1116,12 @@ async function detectSetupState(values, entry) {
|
|
|
981
1116
|
const scope = values.scope === 'project' ? 'project' : 'local';
|
|
982
1117
|
const hostReport = await verifyInstall(defaultPaths());
|
|
983
1118
|
return {
|
|
1119
|
+
entry,
|
|
984
1120
|
projectRoot,
|
|
985
1121
|
git,
|
|
986
1122
|
...(ascRoot ? { ascRoot } : {}),
|
|
987
1123
|
...(values.profile ? { requestedProfile: values.profile } : {}),
|
|
988
|
-
profileCandidates: await availableProfiles(installRoot()),
|
|
1124
|
+
profileCandidates: await availableProfiles(installRoot(), externalProfileRoot()),
|
|
989
1125
|
scope,
|
|
990
1126
|
host: [{ id: 'claude', status: hostReport.status }],
|
|
991
1127
|
// **bootstrap으로 들어왔을 때만 본다.** 설치된 runtime이 자기를 다시 설치할 이유가
|
|
@@ -1101,6 +1237,7 @@ async function inspectSetup(root) {
|
|
|
1101
1237
|
const outcome = await bootstrapGuard({
|
|
1102
1238
|
ascRoot: root,
|
|
1103
1239
|
installRoot: installRoot(),
|
|
1240
|
+
externalProfileRoot: externalProfileRoot(),
|
|
1104
1241
|
capabilities: CAPABILITIES,
|
|
1105
1242
|
adapters: ADAPTER_VERSIONS,
|
|
1106
1243
|
ascVersion: ASC_VERSION,
|
|
@@ -1115,6 +1252,9 @@ async function inspectSetup(root) {
|
|
|
1115
1252
|
const runtime = outcome.ok ? outcome.runtime : undefined;
|
|
1116
1253
|
return assessSetup({
|
|
1117
1254
|
attachment,
|
|
1255
|
+
...(runtime
|
|
1256
|
+
? { profile: { id: runtime.layers.profile.id, origin: runtime.layers.profileOrigin } }
|
|
1257
|
+
: {}),
|
|
1118
1258
|
hasApprovers: Object.keys(await loadIdentityMap(root)).length > 0,
|
|
1119
1259
|
hasControllerIdentities: Object.keys(runtime?.controllerIdentities ?? {}).length > 0,
|
|
1120
1260
|
hasMonitorIdentities: (runtime?.monitor.identities?.length ?? 0) > 0,
|
|
@@ -1129,10 +1269,18 @@ const hasToken = async () => (await discoverToken()) !== null;
|
|
|
1129
1269
|
*/
|
|
1130
1270
|
/** 이 저장소(또는 설치된 패키지)의 뿌리. profiles/ · presets/ 를 여기서 읽는다. */
|
|
1131
1271
|
const installRoot = () => join(dirname(fileURLToPath(import.meta.url)), '..');
|
|
1272
|
+
/**
|
|
1273
|
+
* 사용자 소유 Profile 디렉터리. 팀이 나눠 갖는 실 Profile이 여기 온다 —
|
|
1274
|
+
* 배포본에는 예시만 있고, 남의 프로젝트 설정은 패키지에 실리지 않는다.
|
|
1275
|
+
*
|
|
1276
|
+
* **경로를 아는 것은 Surface의 몫이다.** Core에 홈을 알려 주지 않는다 (C-11).
|
|
1277
|
+
*/
|
|
1278
|
+
const externalProfileRoot = () => join(ascHome(), 'profiles');
|
|
1132
1279
|
async function checkBootstrap(root) {
|
|
1133
1280
|
const outcome = await bootstrapGuard({
|
|
1134
1281
|
ascRoot: root,
|
|
1135
1282
|
installRoot: installRoot(),
|
|
1283
|
+
externalProfileRoot: externalProfileRoot(),
|
|
1136
1284
|
capabilities: CAPABILITIES,
|
|
1137
1285
|
adapters: ADAPTER_VERSIONS,
|
|
1138
1286
|
ascVersion: ASC_VERSION,
|
|
@@ -1160,6 +1308,76 @@ async function checkBootstrap(root) {
|
|
|
1160
1308
|
console.error('\nOnce you have checked it, re-lock with `asc profile resolve --write`.');
|
|
1161
1309
|
return { code: 2 };
|
|
1162
1310
|
}
|
|
1311
|
+
/**
|
|
1312
|
+
* 지금 이 저장소를 설명하는 Profile을 사용자 소유 공간에 만든다 (P0).
|
|
1313
|
+
*
|
|
1314
|
+
* **이것이 되물음을 없앤다.** 배포본에 담긴 Profile은 예시뿐이고, 그래서 URL만 받은 agent는
|
|
1315
|
+
* `ASC_PROFILE_SELECTION_REQUIRED` 앞에서 고를 것이 없어 사람에게 물었다. 여기서 만드는 것은
|
|
1316
|
+
* git remote가 증명하는 사실뿐이다 — 정본 branch·role 경계·정책은 짓지 않는다 (adopt.ts 주석).
|
|
1317
|
+
*
|
|
1318
|
+
* 쓰는 곳은 `$ASC_HOME/profiles/<id>/` 이고 저장소는 건드리지 않는다. 이미 있으면 덮지 않고
|
|
1319
|
+
* 멈춘다 — 남이 쓰던 Profile을 조용히 갈아 끼우는 것이 이 명령의 일이 아니다.
|
|
1320
|
+
*/
|
|
1321
|
+
async function runProfileAdopt(values, entry) {
|
|
1322
|
+
const asJson = Boolean(values.json) || Boolean(values.agent);
|
|
1323
|
+
const { root: projectRoot, git } = await discoverProjectRoot(process.cwd());
|
|
1324
|
+
const remotes = git ? await gitRemotes(projectRoot) : [];
|
|
1325
|
+
let adopted;
|
|
1326
|
+
try {
|
|
1327
|
+
adopted = buildAdoptedProfile({
|
|
1328
|
+
dirName: basename(projectRoot),
|
|
1329
|
+
remotes,
|
|
1330
|
+
// provider를 아는 것은 Adapter를 아는 이 층이다 (C-09 §6.1). host 하나로 단정하는
|
|
1331
|
+
// 것은 여기까지고, 모르는 host는 `git` 이라고만 적는다 — 그것이 사실이다.
|
|
1332
|
+
scmForHost: (host) => (host === 'github.com' ? 'github' : 'git'),
|
|
1333
|
+
...(values.id ? { requestedId: values.id } : {}),
|
|
1334
|
+
});
|
|
1335
|
+
}
|
|
1336
|
+
catch (error) {
|
|
1337
|
+
if (!(error instanceof AdoptError))
|
|
1338
|
+
throw error;
|
|
1339
|
+
console.error(error.message);
|
|
1340
|
+
return 2;
|
|
1341
|
+
}
|
|
1342
|
+
// 스스로 만든 것이 스키마를 통과하는지 **쓰기 전에** 본다. 통과하지 못하는 파일을 놓고
|
|
1343
|
+
// 나가면 그 다음 명령이 남의 설정 오류처럼 죽는다 (95250da가 닫은 것과 같은 모양).
|
|
1344
|
+
const parsed = ProjectProfile.safeParse(adopted.profile);
|
|
1345
|
+
if (!parsed.success) {
|
|
1346
|
+
console.error(`Built a profile that ASC itself rejects — this is a bug in \`profile adopt\`:`);
|
|
1347
|
+
for (const issue of parsed.error.issues)
|
|
1348
|
+
console.error(` - ${issue.path.join('.')}: ${issue.message}`);
|
|
1349
|
+
return 1;
|
|
1350
|
+
}
|
|
1351
|
+
const dir = join(externalProfileRoot(), adopted.id);
|
|
1352
|
+
const path = join(dir, 'profile.json');
|
|
1353
|
+
if (existsSync(path)) {
|
|
1354
|
+
console.error(`A profile called '${adopted.id}' is already there: ${path}\n` +
|
|
1355
|
+
`Attach with it (\`asc setup apply --profile ${adopted.id}\`), or adopt under another name with --id <name>.`);
|
|
1356
|
+
return 1;
|
|
1357
|
+
}
|
|
1358
|
+
await mkdir(dir, { recursive: true });
|
|
1359
|
+
await writeFile(path, `${JSON.stringify(adopted.profile, null, 2)}\n`, 'utf8');
|
|
1360
|
+
// 다음 한 걸음은 두 형태로 준다 — agent는 portable, 사람은 display (C-14 불변식 ⑯).
|
|
1361
|
+
// 여기서는 설치 상태를 다시 관측하지 않는다: 이 명령이 도는 방식이 곧 그 답이다.
|
|
1362
|
+
const args = ['setup', 'apply', '--profile', adopted.id];
|
|
1363
|
+
// portable은 agent가 그대로 실행한다 — 기계가 읽는 형태로 끝난다 (setup-plan.ts와 같은 규칙).
|
|
1364
|
+
const machine = [...args, '--json'];
|
|
1365
|
+
const action = {
|
|
1366
|
+
type: 'apply_setup',
|
|
1367
|
+
display: shorthandCommand(args),
|
|
1368
|
+
portable: entry === 'bootstrap' ? portableCommand(machine) : shorthandCommand(machine),
|
|
1369
|
+
};
|
|
1370
|
+
if (asJson) {
|
|
1371
|
+
console.log(JSON.stringify({ id: adopted.id, path, project: parsed.data.project, warnings: adopted.warnings, nextActions: [action.portable], actions: [action] }, null, 2));
|
|
1372
|
+
return 0;
|
|
1373
|
+
}
|
|
1374
|
+
console.log(`Adopted ${projectRoot} as profile '${adopted.id}' — ${path}`);
|
|
1375
|
+
console.log(`Project: ${parsed.data.project.scm} ${parsed.data.project.repository}`);
|
|
1376
|
+
for (const warning of adopted.warnings)
|
|
1377
|
+
console.log(` note: ${warning}`);
|
|
1378
|
+
console.log(`\nAttach with it: ${action.display}`);
|
|
1379
|
+
return 0;
|
|
1380
|
+
}
|
|
1163
1381
|
/**
|
|
1164
1382
|
* 계층을 합쳐 산출물 셋을 만든다. 기본은 미리보기다 — 무엇이 바뀌는지 보고 나서 쓴다.
|
|
1165
1383
|
* lock이 어긋나면 알리기만 하고 저절로 맞추지 않는다 (OM §4.9).
|
|
@@ -1176,6 +1394,7 @@ async function runProfile(command, values, root) {
|
|
|
1176
1394
|
const installPath = values.install ?? installRoot();
|
|
1177
1395
|
const layers = await loadLayers({
|
|
1178
1396
|
installRoot: installPath,
|
|
1397
|
+
externalProfileRoot: externalProfileRoot(),
|
|
1179
1398
|
profileId: values.profile,
|
|
1180
1399
|
...(values.preset ? { presetId: values.preset } : {}),
|
|
1181
1400
|
overridePath: join(root, 'override.json'),
|
|
@@ -2724,15 +2943,15 @@ async function runFreeze(group, command, values, store) {
|
|
|
2724
2943
|
console.log(removed ? `${id} removed from the list — execution goes through the approval path` : `${id} was not found`);
|
|
2725
2944
|
return removed ? 0 : 1;
|
|
2726
2945
|
}
|
|
2727
|
-
console.error(`Unknown freeze command: ${command}
|
|
2728
|
-
|
|
2946
|
+
console.error(`Unknown freeze command: ${command}
|
|
2947
|
+
|
|
2729
2948
|
${USAGE}`);
|
|
2730
2949
|
return 2;
|
|
2731
2950
|
}
|
|
2732
2951
|
async function runFront(command, values, store, root) {
|
|
2733
2952
|
if (command !== undefined && command !== 'status') {
|
|
2734
|
-
console.error(`Unknown front command: ${command}
|
|
2735
|
-
|
|
2953
|
+
console.error(`Unknown front command: ${command}
|
|
2954
|
+
|
|
2736
2955
|
${USAGE}`);
|
|
2737
2956
|
return 2;
|
|
2738
2957
|
}
|
|
@@ -2910,5 +3129,15 @@ const invokedDirectly = (() => {
|
|
|
2910
3129
|
return false;
|
|
2911
3130
|
}
|
|
2912
3131
|
})();
|
|
2913
|
-
if (invokedDirectly)
|
|
2914
|
-
|
|
3132
|
+
if (invokedDirectly) {
|
|
3133
|
+
try {
|
|
3134
|
+
process.exitCode = await runAscCommand(process.argv.slice(2));
|
|
3135
|
+
}
|
|
3136
|
+
catch (error) {
|
|
3137
|
+
const explained = explainConfigError(error);
|
|
3138
|
+
if (explained === null)
|
|
3139
|
+
throw error; // 모르는 고장은 감추지 않는다
|
|
3140
|
+
console.error(explained);
|
|
3141
|
+
process.exitCode = 1;
|
|
3142
|
+
}
|
|
3143
|
+
}
|