@asc-agent/runtime 0.1.0 → 0.2.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 +4 -1
- package/dist/cli/asc.d.ts +0 -7
- package/dist/cli/asc.js +308 -148
- 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 +1 -1
- package/dist/core/attach/setup-plan.js +20 -3
- package/dist/core/attach/setup.d.ts +10 -0
- package/dist/core/attach/setup.js +10 -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
|
@@ -12,7 +12,7 @@ import { existsSync, 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
16
|
import { GitHubClient, discoverToken } from "../adapters/github/client.js";
|
|
17
17
|
import { GitHubChangeContext, GitHubInventory, GitHubResourceContext } from "../adapters/github/context.js";
|
|
18
18
|
import { GitHubEventSource } from "../adapters/github/event-source.js";
|
|
@@ -25,6 +25,7 @@ import { Executor } from "../core/execution/executor.js";
|
|
|
25
25
|
import { GrantService } from "../core/execution/grant.js";
|
|
26
26
|
import { DecisionKind } from "../core/model/entities.js";
|
|
27
27
|
import { discoverProjectRoot, excludeFromGit, identitiesTemplate, overrideTemplate, writeIfAbsent } from "../core/attach/init.js";
|
|
28
|
+
import { AdoptError, buildAdoptedProfile } from "../core/attach/adopt.js";
|
|
28
29
|
import { locatorsOf, lookupLocator, readIndex, register, writeIndex } from "../core/workspace/index-store.js";
|
|
29
30
|
import { adoptionLine, judgeAdoption, migrate } from "../core/workspace/migrate.js";
|
|
30
31
|
import { newWorkspaceId, normalizeRemote, recoverCandidates, recoverLines } from "../core/workspace/identity.js";
|
|
@@ -47,6 +48,7 @@ import { buildEventObservation } from "../composition/observe.js";
|
|
|
47
48
|
import { availableProfiles, planBootstrap, renderPlan } from "../core/attach/bootstrap.js";
|
|
48
49
|
import { detectStableInstall, installStableRuntime, verifyStableInstall, } from "../core/distribution/runtime-install.js";
|
|
49
50
|
import { readRuntimeSelection, remediationAction, remediationLines, resolveRuntimeTarget, runtimeSelectionLine, selectionPath, writeRuntimeSelection, } from "../core/distribution/runtime-select.js";
|
|
51
|
+
import { portableCommand, shorthandCommand } from "../core/distribution/release.js";
|
|
50
52
|
import { preflight } from "../core/operator/preflight.js";
|
|
51
53
|
import { lookupAuthority } from "../core/policy/ownership.js";
|
|
52
54
|
import { renderProgress } from "../core/operator/render.js";
|
|
@@ -66,137 +68,139 @@ import { FreezeLedger, freezeLines, judgeAction } from "../core/policy/remote-fr
|
|
|
66
68
|
import { SessionRuntime } from "../core/runtime/session.js";
|
|
67
69
|
import { Checkpoint, Handoff, SessionRole } from "../core/model/entities.js";
|
|
68
70
|
import { archiveLock, bootstrapGuard, buildLock, compareLock, loadLayers, resolveRuntime } from "../core/resolver/load.js";
|
|
71
|
+
import { ProfileSourceError } from "../core/resolver/profile-source.js";
|
|
69
72
|
import { renderAscMd, renderControllerMd } from "../core/resolver/render.js";
|
|
70
|
-
import { ProfileLock } from "../schemas/profile.js";
|
|
73
|
+
import { ProfileLock, ProjectProfile } from "../schemas/profile.js";
|
|
71
74
|
import { LocalOperator } from "../core/operator/local-operator.js";
|
|
72
75
|
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
|
-
|
|
76
|
+
const USAGE = `asc — Agent Session Control
|
|
77
|
+
|
|
78
|
+
asc proceed [--session <id>] [--goal <text>] [--json]
|
|
79
|
+
|
|
80
|
+
asc inbox list [--all] [--priority P0|P1|P2] [--json]
|
|
81
|
+
asc inbox show <REQUEST_ID> [--json]
|
|
82
|
+
asc inbox trace <REQUEST_ID> [--json] # how it got here — an exploratory trace
|
|
83
|
+
asc inbox digest [--flush] [--json] # batched view (P0 stays separate)
|
|
84
|
+
asc inbox latest [--priority P0|P1|P2] [--json]
|
|
85
|
+
asc inbox decide <REQUEST_ID> <approve|revise|defer|dismiss|queue> --as <actor>
|
|
86
|
+
[--revision <text>] [--expect <version>]
|
|
87
|
+
|
|
88
|
+
asc grant issue <REQUEST_ID> --action <key> --target <ref> --as <actor>
|
|
89
|
+
[--grant-id <id>] [--expires <iso>]
|
|
90
|
+
asc grant run <GRANT_ID> [--run-id <id>]
|
|
91
|
+
|
|
92
|
+
asc monitor scan [--backfill] [--as <controller>] # fast path
|
|
93
|
+
asc monitor reconcile [--as <controller>] # recover what was missed — re-list
|
|
94
|
+
asc monitor census [--as <controller>] # full reconcile + detect disappearances
|
|
95
|
+
asc monitor status [--json] # how far coverage has been confirmed
|
|
96
|
+
|
|
97
|
+
asc profile adopt [--id <name>] [--json] # make a profile for this repository
|
|
98
|
+
asc profile resolve --profile <id> [--preset <id>] [--install <path>] [--write]
|
|
99
|
+
|
|
100
|
+
asc runtime start [--interval-min <n>] [--delta-min <n>] [--reconcile-min <n>]
|
|
101
|
+
[--census-min <n>] [--digest-min <n>]
|
|
102
|
+
asc runtime tick
|
|
103
|
+
asc runtime status [--json] # which build is in use (C-14 §4)
|
|
104
|
+
asc runtime use package
|
|
105
|
+
asc runtime use development <checkout> # run a built checkout instead
|
|
106
|
+
asc front [status] [--json]
|
|
107
|
+
asc escalate open <S-ID> --predicate <p>... --question <t> --blocked <node>...
|
|
108
|
+
--evidence <ref>... [--blocked-scope <path>...] [--previous <ESC-ID> --why <t>]
|
|
109
|
+
asc escalate list
|
|
110
|
+
asc escalate resolve <ESC-ID>
|
|
111
|
+
asc freeze [status]
|
|
112
|
+
asc freeze on --reason <text> [--offline]
|
|
113
|
+
asc freeze defer --id <id> --intent <text> [--evidence <ref>]
|
|
114
|
+
asc freeze release --id <id>
|
|
115
|
+
asc thaw
|
|
116
|
+
asc workspace list
|
|
117
|
+
asc workspace migrate [--force]
|
|
118
|
+
asc init [--profile <id>] [--preset <id>] [--install <path>]
|
|
119
|
+
[--scope local|project] [--workspace <W-id>]
|
|
120
|
+
# without --profile: report what was detected, then stop
|
|
121
|
+
|
|
122
|
+
asc setup status [--json]
|
|
123
|
+
asc setup plan [--profile <id>] [--scope local|project] [--json]
|
|
124
|
+
# says what it would change — changes nothing
|
|
125
|
+
asc setup apply [--profile <id>] [--scope local|project] [--json]
|
|
126
|
+
asc setup --agent # non-interactive apply. stdout is a single JSON document
|
|
127
|
+
|
|
128
|
+
asc session issue <ID> --role <role> --goal <text> [--block <id>]
|
|
129
|
+
[--parent <S-ID>] [--issued-by <principal>]
|
|
130
|
+
asc session pause <S-ID> --position <t> --next <t> [--physical <id>]
|
|
131
|
+
[--judgment <t>] [--blocker <t>] [--risk <t>] [--evidence <ref>]
|
|
132
|
+
asc session validate <target S-ID> --validator <validator S-ID> --result PASS|FAIL [--finding <t>]
|
|
133
|
+
asc session audit <S-ID>
|
|
134
|
+
asc session report <S-ID> [--json]
|
|
135
|
+
asc session decision <S-ID> --class <c> --selected <t> --why <t>... --evidence <ref>...
|
|
136
|
+
[--alternative <t>...] [--ownership <scope>...] [--verification <t>...]
|
|
137
|
+
[--boundary <glob>...] [--exception <item>...]
|
|
138
|
+
[--criteria <text>...] [--owner <role>]
|
|
139
|
+
[--domain <decision-domain>...] [--authority <domain>=<role>...]
|
|
140
|
+
[--dependency <text>...]
|
|
141
|
+
asc session start <ID>
|
|
142
|
+
asc session pause <ID> --position <text> --next <text> [--done <task>...]
|
|
143
|
+
asc session resume <ID>
|
|
144
|
+
asc session done <ID> --verified <text> --next <text> [--done <task>...]
|
|
145
|
+
[--changed <path>...] [--unresolved <text>...]
|
|
146
|
+
asc session list
|
|
147
|
+
|
|
148
|
+
asc controller collect
|
|
149
|
+
|
|
150
|
+
asc closure list [<S-ID>]
|
|
151
|
+
asc closure confirm <S-ID> --item <id>...
|
|
152
|
+
|
|
153
|
+
asc preflight --path <p>... (--role <r> | --session <S-ID>) [--json]
|
|
154
|
+
|
|
155
|
+
asc query open <X-ID> --session <S-ID> --domain <decision-domain>
|
|
156
|
+
--question <text> [--context <text>] [--default <text>]
|
|
157
|
+
[--blocking <text>] [--expect-response DECIDE|ANSWER]
|
|
158
|
+
[--in-reply-to <X-ID>]
|
|
159
|
+
asc query answer <X-ID> --kind DECIDE|ANSWER|ESCALATE --by <role> --body <text>
|
|
160
|
+
[--to <authority>]
|
|
161
|
+
asc query list [--json]
|
|
162
|
+
|
|
163
|
+
asc progress show [<S-ID>]
|
|
164
|
+
asc progress report <S-ID> --physical <id> --phase <text>
|
|
165
|
+
[--milestone <text>...] [--next <text>] [--unresolved <text>...]
|
|
166
|
+
[--decision none|later|now] [--decision-ref <text>]
|
|
167
|
+
[--verifier none|running|pass|fail] [--verifier-detail <text>] [--terminal]
|
|
168
|
+
|
|
169
|
+
asc host claude install [--force] # --force: overwrite ASC files a person has edited
|
|
170
|
+
asc host claude uninstall|probe [--report <cap>=<bool>...]
|
|
171
|
+
asc host claude guard
|
|
172
|
+
asc host claude bind <S-ID> --physical <id> [--principal <p>] [--worker <id>] [--kind <k>] [--force]
|
|
173
|
+
asc host claude release <S-ID> --physical <id>
|
|
174
|
+
asc host claude contract <S-ID>
|
|
175
|
+
|
|
176
|
+
Options
|
|
177
|
+
--root <path> runtime directory (otherwise: registered workspace, then repo-local .asc)
|
|
178
|
+
--json machine-readable output
|
|
179
|
+
--as <actor> who is deciding. Must be mapped as an approver
|
|
180
|
+
--revision what was changed, when approving with revisions
|
|
181
|
+
--expect the version you read. Rejected if it changed since
|
|
182
|
+
--action action key to emit (e.g. github.issue_comment.create)
|
|
183
|
+
--target target reference (e.g. owner/repo#19)
|
|
184
|
+
--backfill sweep history on the first run (default: from now on)
|
|
185
|
+
--profile Project Profile id
|
|
186
|
+
--preset Operational Preset id
|
|
187
|
+
--install ASC installation path (default: where this CLI lives)
|
|
188
|
+
--write actually write the artefacts (default: preview)
|
|
189
|
+
--role planner|researcher|implementer|verifier
|
|
190
|
+
--goal the single goal of this session
|
|
191
|
+
--boundary write scope (must be narrower than the Profile's)
|
|
192
|
+
--exception SOFT DENY item allowed for this session only
|
|
193
|
+
--criteria a verifiable done-criterion (repeatable)
|
|
194
|
+
--path output path to check (preflight, repeatable)
|
|
195
|
+
--item confirmed closure item id (closure confirm, repeatable)
|
|
196
|
+
--phase one line on what is happening right now (progress report)
|
|
197
|
+
--milestone a meaningfully finished chunk (repeatable)
|
|
198
|
+
--decision does a person need to decide: none|later|now
|
|
199
|
+
--verifier independent verification state: none|running|pass|fail
|
|
200
|
+
--terminal final report — stays as the closing screen after collect
|
|
201
|
+
|
|
202
|
+
decide assumes a person is operating it, and only checks that the name given with --as
|
|
203
|
+
is registered as an approver. Approval is not permission to publish: anything reaching an
|
|
200
204
|
external system goes out through a separate Execution Grant.`;
|
|
201
205
|
/**
|
|
202
206
|
* 지금 여기가 어느 ASC runtime인가. **모든 명령이 같은 문을 지난다** (C-11 §3, B-45).
|
|
@@ -300,6 +304,34 @@ const DECISION_ERROR = {
|
|
|
300
304
|
* 진입이 둘이어도 판단은 하나다. 그래서 export이고, 그래서 아래 자동 실행은 이 파일이
|
|
301
305
|
* 진짜 진입점일 때만 돈다.
|
|
302
306
|
*/
|
|
307
|
+
/**
|
|
308
|
+
* 설정을 읽다 실패한 것을 **사람이 읽을 문장**으로 바꾼다.
|
|
309
|
+
*
|
|
310
|
+
* 여기가 없으면 Profile 하나가 잘못됐을 때 사용자가 보는 것은 Node의 stack dump다 —
|
|
311
|
+
* 내부 파일 이름과 프레임이 줄줄이 나오고, 정작 "무엇을 고쳐야 하는지"는 없다.
|
|
312
|
+
* 독립 검증이 다섯 갈래(충돌·깨진 JSON·디렉터리가 아닌 것·EISDIR·긴 id)에서 같은 모양을
|
|
313
|
+
* 관측했다. 예상 못 한 오류는 그대로 던진다 — 삼키면 그게 더 나쁘다.
|
|
314
|
+
*/
|
|
315
|
+
function explainConfigError(error) {
|
|
316
|
+
if (error instanceof ProfileSourceError)
|
|
317
|
+
return error.message;
|
|
318
|
+
const failure = error;
|
|
319
|
+
const path = failure?.path ? ` (${failure.path})` : '';
|
|
320
|
+
switch (failure?.code) {
|
|
321
|
+
case 'ENOENT':
|
|
322
|
+
return `That profile is not there${path}. \`asc setup status\` lists what is.`;
|
|
323
|
+
case 'EISDIR':
|
|
324
|
+
return `A profile has to be a file, and that is a directory${path}.`;
|
|
325
|
+
case 'EACCES':
|
|
326
|
+
case 'EPERM':
|
|
327
|
+
return `No permission to read that profile${path}.`;
|
|
328
|
+
default:
|
|
329
|
+
break;
|
|
330
|
+
}
|
|
331
|
+
if (error instanceof SyntaxError)
|
|
332
|
+
return `That profile is not valid JSON — ${error.message}`;
|
|
333
|
+
return null;
|
|
334
|
+
}
|
|
303
335
|
export async function runAscCommand(argv, entry = 'runtime') {
|
|
304
336
|
const { values, positionals } = parseArgs({
|
|
305
337
|
args: argv,
|
|
@@ -427,6 +459,10 @@ export async function runAscCommand(argv, entry = 'runtime') {
|
|
|
427
459
|
// 그러면 "아직 안 붙었다"를 확인하려고 부른 명령이 안 붙었다는 이유로 죽는다.
|
|
428
460
|
if (group === 'setup')
|
|
429
461
|
return runSetup(command, values, entry);
|
|
462
|
+
// adopt는 **붙기 전의 명령이다.** 붙을 Profile을 만드는 것이 일이므로 attach를 요구하면
|
|
463
|
+
// 순서가 뒤집힌다. 나머지 profile 명령은 아래 attach 경로에 그대로 남는다.
|
|
464
|
+
if (group === 'profile' && command === 'adopt')
|
|
465
|
+
return runProfileAdopt(values, entry);
|
|
430
466
|
if (!['inbox', 'grant', 'monitor', 'runtime', 'front', 'freeze', 'thaw', 'escalate', 'profile', 'session', 'controller', 'proceed', 'progress', 'preflight', 'closure', 'query'].includes(group)) {
|
|
431
467
|
console.error(`Unknown command: ${group}\n\n${USAGE}`);
|
|
432
468
|
return 2;
|
|
@@ -616,7 +652,7 @@ async function attachLocalWorkspace(projectRoot, git, declaredWorkspace) {
|
|
|
616
652
|
return existing.root;
|
|
617
653
|
}
|
|
618
654
|
const remotes = git ? await gitRemotes(projectRoot) : [];
|
|
619
|
-
const aliases = remotes
|
|
655
|
+
const aliases = remoteAliases(remotes);
|
|
620
656
|
// 사람이 "이건 그 프로젝트다"라고 말한 경우 — 이어붙인다. 추론이 아니라 선언이다.
|
|
621
657
|
if (declaredWorkspace) {
|
|
622
658
|
const known = index.workspaces[declaredWorkspace];
|
|
@@ -635,7 +671,7 @@ async function attachLocalWorkspace(projectRoot, git, declaredWorkspace) {
|
|
|
635
671
|
console.log(`Registered this location under workspace ${known.workspaceId} — runtime lives at ${root}`);
|
|
636
672
|
return root;
|
|
637
673
|
}
|
|
638
|
-
const hits = recoverCandidates(Object.values(index.workspaces),
|
|
674
|
+
const hits = recoverCandidates(Object.values(index.workspaces), aliases);
|
|
639
675
|
if (hits.length > 0) {
|
|
640
676
|
// 붙일지는 사람이 정한다. 여기서 이어붙이면 남의 workspace를 조용히 가져올 수 있다.
|
|
641
677
|
for (const line of recoverLines(hits))
|
|
@@ -658,20 +694,33 @@ async function attachLocalWorkspace(projectRoot, git, declaredWorkspace) {
|
|
|
658
694
|
console.log(`Recognisable as: ${aliases.join(', ')}`);
|
|
659
695
|
return root;
|
|
660
696
|
}
|
|
661
|
-
/**
|
|
697
|
+
/**
|
|
698
|
+
* 모든 remote를 evidence로 모은다. origin을 primary로 단정하지 않는다 (C-11 불변식 ④).
|
|
699
|
+
*
|
|
700
|
+
* 이름을 함께 든다 — identity alias는 이름이 필요 없지만, `profile adopt` 는 어느 remote가
|
|
701
|
+
* 이 프로젝트를 대표하는지 골라야 하고 `git remote -v` 의 출력 순서는 알파벳순이라
|
|
702
|
+
* "첫 줄이 origin"이 아니다.
|
|
703
|
+
*/
|
|
662
704
|
async function gitRemotes(projectRoot) {
|
|
663
705
|
try {
|
|
664
706
|
const { stdout } = await execFileAsync('git', ['-C', projectRoot, 'remote', '-v']);
|
|
665
|
-
const
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
707
|
+
const seen = new Map();
|
|
708
|
+
for (const line of stdout.split(/\r?\n/)) {
|
|
709
|
+
const [name, url] = line.split(/\s+/);
|
|
710
|
+
if (!name || !url || seen.has(`${name}${url}`))
|
|
711
|
+
continue;
|
|
712
|
+
seen.set(`${name}${url}`, { name, url });
|
|
713
|
+
}
|
|
714
|
+
return [...seen.values()];
|
|
670
715
|
}
|
|
671
716
|
catch {
|
|
672
717
|
return [];
|
|
673
718
|
}
|
|
674
719
|
}
|
|
720
|
+
/** identity alias는 이름을 쓰지 않는다 — URL만 정규화한다. */
|
|
721
|
+
const remoteAliases = (remotes) => [...new Set(remotes.map((remote) => remote.url))]
|
|
722
|
+
.map(normalizeRemote)
|
|
723
|
+
.filter((alias) => alias !== null);
|
|
675
724
|
/**
|
|
676
725
|
* 저장소 안에 있던 `.asc/` 를 사용자 소유 공간으로 옮긴다 (C-11 §6).
|
|
677
726
|
*
|
|
@@ -679,8 +728,8 @@ async function gitRemotes(projectRoot) {
|
|
|
679
728
|
*/
|
|
680
729
|
async function runWorkspace(command, values) {
|
|
681
730
|
if (command !== 'migrate' && command !== 'list') {
|
|
682
|
-
console.error(`Unknown workspace command: ${command ?? '(none)'}
|
|
683
|
-
|
|
731
|
+
console.error(`Unknown workspace command: ${command ?? '(none)'}
|
|
732
|
+
|
|
684
733
|
${USAGE}`);
|
|
685
734
|
return 2;
|
|
686
735
|
}
|
|
@@ -726,7 +775,7 @@ ${USAGE}`);
|
|
|
726
775
|
workspaceId,
|
|
727
776
|
root: target,
|
|
728
777
|
locator: { path: projectRoot, platform: process.platform, observedAt: new Date().toISOString() },
|
|
729
|
-
aliases: remotes
|
|
778
|
+
aliases: remoteAliases(remotes),
|
|
730
779
|
now: new Date().toISOString(),
|
|
731
780
|
}));
|
|
732
781
|
console.log(`Copied and verified ${outcome.plan.entries} entries into ${target} (workspace ${workspaceId}).`);
|
|
@@ -773,6 +822,7 @@ async function runInit(values) {
|
|
|
773
822
|
const plan = await planBootstrap({
|
|
774
823
|
cwd: process.cwd(),
|
|
775
824
|
installRoot: installRoot(),
|
|
825
|
+
externalProfileRoot: externalProfileRoot(),
|
|
776
826
|
...(attachedRoot ? { ascRoot: attachedRoot } : {}),
|
|
777
827
|
hosts: [{ id: 'claude', installed: await verifyInstalled(defaultPaths()) }],
|
|
778
828
|
bindings,
|
|
@@ -788,6 +838,24 @@ async function runInit(values) {
|
|
|
788
838
|
}
|
|
789
839
|
const { root: projectRoot, git } = await discoverProjectRoot(process.cwd());
|
|
790
840
|
console.log(`Project: ${projectRoot}${git ? '' : ' (not a git repository)'}`);
|
|
841
|
+
// **읽을 수 있는 Profile인지 먼저 본다.** 아래부터는 `.git/info/exclude` 를 고치고
|
|
842
|
+
// 템플릿을 만드는 등 세상을 바꾸는 일이고, 그 뒤에 Profile이 잘못된 것을 알면 반쯤 만든
|
|
843
|
+
// `.asc/` 가 남는다 — 독립 검증이 실제로 그 상태를 만들었다. 여기서 멈추면 아무것도 남지 않는다.
|
|
844
|
+
try {
|
|
845
|
+
await loadLayers({
|
|
846
|
+
installRoot: installRoot(),
|
|
847
|
+
externalProfileRoot: externalProfileRoot(),
|
|
848
|
+
profileId: values.profile,
|
|
849
|
+
});
|
|
850
|
+
}
|
|
851
|
+
catch (error) {
|
|
852
|
+
const explained = explainConfigError(error);
|
|
853
|
+
if (explained === null)
|
|
854
|
+
throw error;
|
|
855
|
+
console.error(explained);
|
|
856
|
+
console.error('Nothing was changed.');
|
|
857
|
+
return 2;
|
|
858
|
+
}
|
|
791
859
|
const ascRoot = scope === 'project'
|
|
792
860
|
? join(projectRoot, '.asc')
|
|
793
861
|
: await attachLocalWorkspace(projectRoot, git, values.workspace);
|
|
@@ -824,6 +892,7 @@ async function attachedRuntime(ascRoot) {
|
|
|
824
892
|
const outcome = await bootstrapGuard({
|
|
825
893
|
ascRoot,
|
|
826
894
|
installRoot: installRoot(),
|
|
895
|
+
externalProfileRoot: externalProfileRoot(),
|
|
827
896
|
capabilities: CAPABILITIES,
|
|
828
897
|
adapters: ADAPTER_VERSIONS,
|
|
829
898
|
ascVersion: ASC_VERSION,
|
|
@@ -851,7 +920,7 @@ function declaredPolicies(resolved) {
|
|
|
851
920
|
}
|
|
852
921
|
return declared;
|
|
853
922
|
}
|
|
854
|
-
const ASC_VERSION = '0.
|
|
923
|
+
const ASC_VERSION = '0.2.0';
|
|
855
924
|
const CAPABILITIES = ['scm.github', 'state.markdown', 'approval.local'];
|
|
856
925
|
const ADAPTER_VERSIONS = { 'scm.github': ASC_VERSION, 'state.markdown': ASC_VERSION };
|
|
857
926
|
/**
|
|
@@ -985,7 +1054,7 @@ async function detectSetupState(values, entry) {
|
|
|
985
1054
|
git,
|
|
986
1055
|
...(ascRoot ? { ascRoot } : {}),
|
|
987
1056
|
...(values.profile ? { requestedProfile: values.profile } : {}),
|
|
988
|
-
profileCandidates: await availableProfiles(installRoot()),
|
|
1057
|
+
profileCandidates: await availableProfiles(installRoot(), externalProfileRoot()),
|
|
989
1058
|
scope,
|
|
990
1059
|
host: [{ id: 'claude', status: hostReport.status }],
|
|
991
1060
|
// **bootstrap으로 들어왔을 때만 본다.** 설치된 runtime이 자기를 다시 설치할 이유가
|
|
@@ -1101,6 +1170,7 @@ async function inspectSetup(root) {
|
|
|
1101
1170
|
const outcome = await bootstrapGuard({
|
|
1102
1171
|
ascRoot: root,
|
|
1103
1172
|
installRoot: installRoot(),
|
|
1173
|
+
externalProfileRoot: externalProfileRoot(),
|
|
1104
1174
|
capabilities: CAPABILITIES,
|
|
1105
1175
|
adapters: ADAPTER_VERSIONS,
|
|
1106
1176
|
ascVersion: ASC_VERSION,
|
|
@@ -1115,6 +1185,9 @@ async function inspectSetup(root) {
|
|
|
1115
1185
|
const runtime = outcome.ok ? outcome.runtime : undefined;
|
|
1116
1186
|
return assessSetup({
|
|
1117
1187
|
attachment,
|
|
1188
|
+
...(runtime
|
|
1189
|
+
? { profile: { id: runtime.layers.profile.id, origin: runtime.layers.profileOrigin } }
|
|
1190
|
+
: {}),
|
|
1118
1191
|
hasApprovers: Object.keys(await loadIdentityMap(root)).length > 0,
|
|
1119
1192
|
hasControllerIdentities: Object.keys(runtime?.controllerIdentities ?? {}).length > 0,
|
|
1120
1193
|
hasMonitorIdentities: (runtime?.monitor.identities?.length ?? 0) > 0,
|
|
@@ -1129,10 +1202,18 @@ const hasToken = async () => (await discoverToken()) !== null;
|
|
|
1129
1202
|
*/
|
|
1130
1203
|
/** 이 저장소(또는 설치된 패키지)의 뿌리. profiles/ · presets/ 를 여기서 읽는다. */
|
|
1131
1204
|
const installRoot = () => join(dirname(fileURLToPath(import.meta.url)), '..');
|
|
1205
|
+
/**
|
|
1206
|
+
* 사용자 소유 Profile 디렉터리. 팀이 나눠 갖는 실 Profile이 여기 온다 —
|
|
1207
|
+
* 배포본에는 예시만 있고, 남의 프로젝트 설정은 패키지에 실리지 않는다.
|
|
1208
|
+
*
|
|
1209
|
+
* **경로를 아는 것은 Surface의 몫이다.** Core에 홈을 알려 주지 않는다 (C-11).
|
|
1210
|
+
*/
|
|
1211
|
+
const externalProfileRoot = () => join(ascHome(), 'profiles');
|
|
1132
1212
|
async function checkBootstrap(root) {
|
|
1133
1213
|
const outcome = await bootstrapGuard({
|
|
1134
1214
|
ascRoot: root,
|
|
1135
1215
|
installRoot: installRoot(),
|
|
1216
|
+
externalProfileRoot: externalProfileRoot(),
|
|
1136
1217
|
capabilities: CAPABILITIES,
|
|
1137
1218
|
adapters: ADAPTER_VERSIONS,
|
|
1138
1219
|
ascVersion: ASC_VERSION,
|
|
@@ -1160,6 +1241,74 @@ async function checkBootstrap(root) {
|
|
|
1160
1241
|
console.error('\nOnce you have checked it, re-lock with `asc profile resolve --write`.');
|
|
1161
1242
|
return { code: 2 };
|
|
1162
1243
|
}
|
|
1244
|
+
/**
|
|
1245
|
+
* 지금 이 저장소를 설명하는 Profile을 사용자 소유 공간에 만든다 (P0).
|
|
1246
|
+
*
|
|
1247
|
+
* **이것이 되물음을 없앤다.** 배포본에 담긴 Profile은 예시뿐이고, 그래서 URL만 받은 agent는
|
|
1248
|
+
* `ASC_PROFILE_SELECTION_REQUIRED` 앞에서 고를 것이 없어 사람에게 물었다. 여기서 만드는 것은
|
|
1249
|
+
* git remote가 증명하는 사실뿐이다 — 정본 branch·role 경계·정책은 짓지 않는다 (adopt.ts 주석).
|
|
1250
|
+
*
|
|
1251
|
+
* 쓰는 곳은 `$ASC_HOME/profiles/<id>/` 이고 저장소는 건드리지 않는다. 이미 있으면 덮지 않고
|
|
1252
|
+
* 멈춘다 — 남이 쓰던 Profile을 조용히 갈아 끼우는 것이 이 명령의 일이 아니다.
|
|
1253
|
+
*/
|
|
1254
|
+
async function runProfileAdopt(values, entry) {
|
|
1255
|
+
const asJson = Boolean(values.json) || Boolean(values.agent);
|
|
1256
|
+
const { root: projectRoot, git } = await discoverProjectRoot(process.cwd());
|
|
1257
|
+
const remotes = git ? await gitRemotes(projectRoot) : [];
|
|
1258
|
+
let adopted;
|
|
1259
|
+
try {
|
|
1260
|
+
adopted = buildAdoptedProfile({
|
|
1261
|
+
dirName: basename(projectRoot),
|
|
1262
|
+
remotes,
|
|
1263
|
+
// provider를 아는 것은 Adapter를 아는 이 층이다 (C-09 §6.1). host 하나로 단정하는
|
|
1264
|
+
// 것은 여기까지고, 모르는 host는 `git` 이라고만 적는다 — 그것이 사실이다.
|
|
1265
|
+
scmForHost: (host) => (host === 'github.com' ? 'github' : 'git'),
|
|
1266
|
+
...(values.id ? { requestedId: values.id } : {}),
|
|
1267
|
+
});
|
|
1268
|
+
}
|
|
1269
|
+
catch (error) {
|
|
1270
|
+
if (!(error instanceof AdoptError))
|
|
1271
|
+
throw error;
|
|
1272
|
+
console.error(error.message);
|
|
1273
|
+
return 2;
|
|
1274
|
+
}
|
|
1275
|
+
// 스스로 만든 것이 스키마를 통과하는지 **쓰기 전에** 본다. 통과하지 못하는 파일을 놓고
|
|
1276
|
+
// 나가면 그 다음 명령이 남의 설정 오류처럼 죽는다 (95250da가 닫은 것과 같은 모양).
|
|
1277
|
+
const parsed = ProjectProfile.safeParse(adopted.profile);
|
|
1278
|
+
if (!parsed.success) {
|
|
1279
|
+
console.error(`Built a profile that ASC itself rejects — this is a bug in \`profile adopt\`:`);
|
|
1280
|
+
for (const issue of parsed.error.issues)
|
|
1281
|
+
console.error(` - ${issue.path.join('.')}: ${issue.message}`);
|
|
1282
|
+
return 1;
|
|
1283
|
+
}
|
|
1284
|
+
const dir = join(externalProfileRoot(), adopted.id);
|
|
1285
|
+
const path = join(dir, 'profile.json');
|
|
1286
|
+
if (existsSync(path)) {
|
|
1287
|
+
console.error(`A profile called '${adopted.id}' is already there: ${path}\n` +
|
|
1288
|
+
`Attach with it (\`asc setup apply --profile ${adopted.id}\`), or adopt under another name with --id <name>.`);
|
|
1289
|
+
return 1;
|
|
1290
|
+
}
|
|
1291
|
+
await mkdir(dir, { recursive: true });
|
|
1292
|
+
await writeFile(path, `${JSON.stringify(adopted.profile, null, 2)}\n`, 'utf8');
|
|
1293
|
+
// 다음 한 걸음은 두 형태로 준다 — agent는 portable, 사람은 display (C-14 불변식 ⑯).
|
|
1294
|
+
// 여기서는 설치 상태를 다시 관측하지 않는다: 이 명령이 도는 방식이 곧 그 답이다.
|
|
1295
|
+
const args = ['setup', 'apply', '--profile', adopted.id];
|
|
1296
|
+
const action = {
|
|
1297
|
+
type: 'apply_setup',
|
|
1298
|
+
display: shorthandCommand(args),
|
|
1299
|
+
portable: entry === 'bootstrap' ? portableCommand(args) : shorthandCommand(args),
|
|
1300
|
+
};
|
|
1301
|
+
if (asJson) {
|
|
1302
|
+
console.log(JSON.stringify({ id: adopted.id, path, project: parsed.data.project, warnings: adopted.warnings, nextActions: [action.portable], actions: [action] }, null, 2));
|
|
1303
|
+
return 0;
|
|
1304
|
+
}
|
|
1305
|
+
console.log(`Adopted ${projectRoot} as profile '${adopted.id}' — ${path}`);
|
|
1306
|
+
console.log(`Project: ${parsed.data.project.scm} ${parsed.data.project.repository}`);
|
|
1307
|
+
for (const warning of adopted.warnings)
|
|
1308
|
+
console.log(` note: ${warning}`);
|
|
1309
|
+
console.log(`\nAttach with it: ${action.display}`);
|
|
1310
|
+
return 0;
|
|
1311
|
+
}
|
|
1163
1312
|
/**
|
|
1164
1313
|
* 계층을 합쳐 산출물 셋을 만든다. 기본은 미리보기다 — 무엇이 바뀌는지 보고 나서 쓴다.
|
|
1165
1314
|
* lock이 어긋나면 알리기만 하고 저절로 맞추지 않는다 (OM §4.9).
|
|
@@ -1176,6 +1325,7 @@ async function runProfile(command, values, root) {
|
|
|
1176
1325
|
const installPath = values.install ?? installRoot();
|
|
1177
1326
|
const layers = await loadLayers({
|
|
1178
1327
|
installRoot: installPath,
|
|
1328
|
+
externalProfileRoot: externalProfileRoot(),
|
|
1179
1329
|
profileId: values.profile,
|
|
1180
1330
|
...(values.preset ? { presetId: values.preset } : {}),
|
|
1181
1331
|
overridePath: join(root, 'override.json'),
|
|
@@ -2724,15 +2874,15 @@ async function runFreeze(group, command, values, store) {
|
|
|
2724
2874
|
console.log(removed ? `${id} removed from the list — execution goes through the approval path` : `${id} was not found`);
|
|
2725
2875
|
return removed ? 0 : 1;
|
|
2726
2876
|
}
|
|
2727
|
-
console.error(`Unknown freeze command: ${command}
|
|
2728
|
-
|
|
2877
|
+
console.error(`Unknown freeze command: ${command}
|
|
2878
|
+
|
|
2729
2879
|
${USAGE}`);
|
|
2730
2880
|
return 2;
|
|
2731
2881
|
}
|
|
2732
2882
|
async function runFront(command, values, store, root) {
|
|
2733
2883
|
if (command !== undefined && command !== 'status') {
|
|
2734
|
-
console.error(`Unknown front command: ${command}
|
|
2735
|
-
|
|
2884
|
+
console.error(`Unknown front command: ${command}
|
|
2885
|
+
|
|
2736
2886
|
${USAGE}`);
|
|
2737
2887
|
return 2;
|
|
2738
2888
|
}
|
|
@@ -2910,5 +3060,15 @@ const invokedDirectly = (() => {
|
|
|
2910
3060
|
return false;
|
|
2911
3061
|
}
|
|
2912
3062
|
})();
|
|
2913
|
-
if (invokedDirectly)
|
|
2914
|
-
|
|
3063
|
+
if (invokedDirectly) {
|
|
3064
|
+
try {
|
|
3065
|
+
process.exitCode = await runAscCommand(process.argv.slice(2));
|
|
3066
|
+
}
|
|
3067
|
+
catch (error) {
|
|
3068
|
+
const explained = explainConfigError(error);
|
|
3069
|
+
if (explained === null)
|
|
3070
|
+
throw error; // 모르는 고장은 감추지 않는다
|
|
3071
|
+
console.error(explained);
|
|
3072
|
+
process.exitCode = 1;
|
|
3073
|
+
}
|
|
3074
|
+
}
|