@asc-agent/runtime 0.2.0 → 0.3.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 +25 -25
- package/dist/adapters/claude-code/skill.js +249 -235
- package/dist/adapters/gitlab/adapter.d.ts +3 -0
- package/dist/adapters/gitlab/adapter.js +22 -3
- package/dist/adapters/gitlab/client.d.ts +27 -1
- package/dist/adapters/gitlab/client.js +30 -0
- package/dist/adapters/gitlab/ports.d.ts +3 -3
- package/dist/adapters/jam/adapter.d.ts +14 -0
- package/dist/adapters/jam/adapter.js +84 -6
- package/dist/adapters/jam/ports.d.ts +9 -0
- package/dist/adapters/jam/ports.js +49 -2
- package/dist/adapters/local/repo.d.ts +15 -0
- package/dist/adapters/local/repo.js +179 -0
- package/dist/adapters/markdown/state-store.js +13 -1
- package/dist/adapters/memory/state-store.js +4 -0
- package/dist/cli/asc.d.ts +6 -0
- package/dist/cli/asc.js +525 -8
- package/dist/composition/propose.d.ts +19 -0
- package/dist/composition/propose.js +35 -0
- package/dist/composition/runtime.d.ts +2 -0
- package/dist/composition/runtime.js +41 -5
- package/dist/core/attach/init.d.ts +17 -0
- package/dist/core/attach/init.js +28 -0
- package/dist/core/attach/setup-plan.d.ts +9 -0
- package/dist/core/attach/setup-plan.js +16 -5
- 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/investigation.d.ts +16 -0
- package/dist/core/monitor/investigation.js +18 -9
- package/dist/core/operator/contract-draft.d.ts +124 -0
- package/dist/core/operator/contract-draft.js +234 -0
- package/dist/core/operator/derive-draft.d.ts +37 -0
- package/dist/core/operator/derive-draft.js +216 -0
- package/dist/core/operator/proceed.d.ts +79 -0
- package/dist/core/operator/proceed.js +123 -1
- package/dist/core/operator/work-state.d.ts +54 -0
- package/dist/core/operator/work-state.js +145 -0
- package/dist/core/runtime/closure.d.ts +2 -2
- package/dist/ports/local-repo.d.ts +61 -0
- package/dist/ports/local-repo.js +9 -0
- package/dist/ports/resource-context.d.ts +7 -0
- package/dist/schemas/profile.d.ts +2 -2
- package/package.json +2 -2
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import type { Session } from '../model/entities.ts';
|
|
2
2
|
import type { StateStore } from '../../ports/state-store.ts';
|
|
3
|
+
import type { RepoObservation } from '../../ports/local-repo.ts';
|
|
4
|
+
import type { ChangeSummary } from '../../ports/change-context.ts';
|
|
5
|
+
import type { ContextComment, ResourceSnapshot } from '../../ports/resource-context.ts';
|
|
6
|
+
import type { SessionContractDraft, SessionContractPlan } from './contract-draft.ts';
|
|
7
|
+
import { type WorkStateResult } from './work-state.ts';
|
|
3
8
|
import type { CanonicalDrift, SessionRuntime } from '../runtime/session.ts';
|
|
4
9
|
import { type EscalationLedger } from '../runtime/escalation.ts';
|
|
5
10
|
import { type ExecutionVerdict } from '../runtime/execution-state.ts';
|
|
@@ -15,6 +20,57 @@ export type ProceedIntent = {
|
|
|
15
20
|
sessionId?: string;
|
|
16
21
|
/** 후보가 없을 때 초안에 실어 줄 목표 힌트. 확정이 아니다. */
|
|
17
22
|
goal?: string;
|
|
23
|
+
/**
|
|
24
|
+
* 사람이 지목한 작업 항목 (예: 이슈 키). 있으면 후보가 없을 때 **조사부터** 한다 —
|
|
25
|
+
* 빈 초안을 돌려주는 대신, 실제로 할 일이 있는지를 먼저 판정한다.
|
|
26
|
+
*/
|
|
27
|
+
workRef?: string;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* 작업 항목 하나를 실제로 조사해 계약까지 잇는 통로 (P0-D).
|
|
31
|
+
*
|
|
32
|
+
* 전부 주입이다. Operator 는 adapter 도 policy 도 모르고, 여기 담긴 함수들이 하는 판정을
|
|
33
|
+
* 다시 계산하지 않는다 — 범위·책임·발급 권한은 `plan` 이 돌려주는 것을 **읽기만** 한다.
|
|
34
|
+
*/
|
|
35
|
+
export type WorkIngress = {
|
|
36
|
+
/** 작업 항목과 그 둘레를 모은다. 못 모은 것은 'UNAVAILABLE' 로 말한다. */
|
|
37
|
+
gather: (workRef: string) => Promise<{
|
|
38
|
+
workItem?: ResourceSnapshot;
|
|
39
|
+
trackerDone?: boolean;
|
|
40
|
+
comments?: readonly ContextComment[] | 'UNAVAILABLE';
|
|
41
|
+
change?: ChangeSummary | 'UNAVAILABLE';
|
|
42
|
+
dependencies?: readonly {
|
|
43
|
+
reference: string;
|
|
44
|
+
state?: string;
|
|
45
|
+
open?: boolean;
|
|
46
|
+
}[];
|
|
47
|
+
}>;
|
|
48
|
+
/** 저장소 관측. 없으면 'MISSING' 으로 취급되고, 그 상태에서는 어떤 추천도 하지 않는다. */
|
|
49
|
+
observeRepo?: (query: {
|
|
50
|
+
refHint: string;
|
|
51
|
+
paths?: readonly string[];
|
|
52
|
+
}) => Promise<RepoObservation>;
|
|
53
|
+
derive: (input: {
|
|
54
|
+
workRef: string;
|
|
55
|
+
goal?: string;
|
|
56
|
+
workItem: ResourceSnapshot;
|
|
57
|
+
workState: WorkStateResult;
|
|
58
|
+
repo: RepoObservation | 'MISSING';
|
|
59
|
+
/** 이미 쓰인 세션 id — 새 id 가 그 위에 겹치지 않게. */
|
|
60
|
+
existingIds: readonly string[];
|
|
61
|
+
}) => SessionContractDraft;
|
|
62
|
+
/** 이미 쓴 세션 id — 회수돼 보관된 것까지. 새 id 가 그 위에 겹치지 않게 한다. */
|
|
63
|
+
usedIds: () => Promise<readonly string[]>;
|
|
64
|
+
/** planSessionContract 를 policy·ownership·기존 id 로 감싼 것. */
|
|
65
|
+
plan: (draft: SessionContractDraft) => Promise<SessionContractPlan>;
|
|
66
|
+
/** 위임 범위 안에서만 불린다. 발급 경로는 SessionRuntime 하나뿐이다. */
|
|
67
|
+
issue: (draft: SessionContractDraft) => Promise<{
|
|
68
|
+
ok: true;
|
|
69
|
+
sessionId: string;
|
|
70
|
+
} | {
|
|
71
|
+
ok: false;
|
|
72
|
+
detail: string;
|
|
73
|
+
}>;
|
|
18
74
|
};
|
|
19
75
|
/** 후보 나열용 요약 — 사람이 고를 근거까지 함께 준다. */
|
|
20
76
|
export type SessionCandidate = {
|
|
@@ -52,6 +108,24 @@ export type ProceedOutcome = ({
|
|
|
52
108
|
goal: string;
|
|
53
109
|
doneCriteria: string[];
|
|
54
110
|
};
|
|
111
|
+
/** 조사에서 도출한 초안 전문. 조사 없이 온 경우엔 없다. */
|
|
112
|
+
full?: SessionContractDraft;
|
|
113
|
+
/** planSessionContract 판정 원본. 여기 있는 것을 다시 계산하지 않는다. */
|
|
114
|
+
plan?: SessionContractPlan;
|
|
115
|
+
/** 발급이 Controller 것일 때 사람이 그대로 실행할 명령. */
|
|
116
|
+
forController?: string[];
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* 조사해 보니 **세션을 낼 일이 아니다** (P0-B). 이미 구현돼 tracker 만 뒤처졌거나,
|
|
120
|
+
* 선행 작업·외부 검증에 막혔거나, 결론 요건이 모자란 경우다.
|
|
121
|
+
*
|
|
122
|
+
* 아무것도 쓰지 않는다. 파생 뷰이며 저장되지 않는다 — 상태 enum(OM §11.2)을 건드리지 않는다.
|
|
123
|
+
*/
|
|
124
|
+
| {
|
|
125
|
+
kind: 'WORK_STATE';
|
|
126
|
+
workRef: string;
|
|
127
|
+
result: WorkStateResult;
|
|
128
|
+
nextAction: string;
|
|
55
129
|
}
|
|
56
130
|
/**
|
|
57
131
|
* 미해소 상신이 실행 가능한 node를 전부 덮었다 (C-13 §6).
|
|
@@ -84,6 +158,11 @@ export type OperatorDeps = {
|
|
|
84
158
|
* 주지 않으면 예전처럼 상태만 보고 간다(기존 호출자 무손상).
|
|
85
159
|
*/
|
|
86
160
|
escalations?: EscalationLedger;
|
|
161
|
+
/**
|
|
162
|
+
* 작업 항목 통로 (P0-D). 없으면 후보가 없을 때 예전처럼 빈 초안을 제안한다 —
|
|
163
|
+
* 기존 호출자 무손상.
|
|
164
|
+
*/
|
|
165
|
+
ingress?: WorkIngress;
|
|
87
166
|
/**
|
|
88
167
|
* 필수다. 모든 진입이 bootstrap/profile.lock 검증을 지난다 — Surface가 어디든.
|
|
89
168
|
* 실 조립은 factory(cli의 createOperator)가 bootstrapGuard로 고정한다.
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
//
|
|
7
7
|
// guard는 optional이 아니다. CLI가 아닌 Host Adapter가 직접 불러도 profile.lock 검증을
|
|
8
8
|
// 건너뛸 수 없어야 하며(C-03 §1.2), 정본 조립은 Surface가 아니라 factory가 진다.
|
|
9
|
+
import { judgeWorkState } from "./work-state.js";
|
|
10
|
+
import { extractPathHints } from "./derive-draft.js";
|
|
9
11
|
import { proceedGateFacts } from "../runtime/escalation.js";
|
|
10
12
|
import { deriveExecutionState } from "../runtime/execution-state.js";
|
|
11
13
|
const RUNNABLE = new Set(['READY', 'PAUSED', 'ACTIVE']);
|
|
@@ -13,11 +15,13 @@ export class Operator {
|
|
|
13
15
|
#store;
|
|
14
16
|
#sessions;
|
|
15
17
|
#escalations;
|
|
18
|
+
#ingress;
|
|
16
19
|
#guard;
|
|
17
20
|
constructor(deps) {
|
|
18
21
|
this.#store = deps.store;
|
|
19
22
|
this.#sessions = deps.sessions;
|
|
20
23
|
this.#escalations = deps.escalations;
|
|
24
|
+
this.#ingress = deps.ingress;
|
|
21
25
|
this.#guard = deps.guard;
|
|
22
26
|
}
|
|
23
27
|
async proceed(intent = {}) {
|
|
@@ -33,8 +37,15 @@ export class Operator {
|
|
|
33
37
|
return this.#advance(session);
|
|
34
38
|
}
|
|
35
39
|
// 자동 탐색 — 실행 가능한 것만 후보다. archive는 collect가 이미 치웠다.
|
|
36
|
-
const
|
|
40
|
+
const runnable = (await this.#store.list('session')).filter((s) => RUNNABLE.has(s.status));
|
|
41
|
+
// 사람이 작업 항목을 지목했으면 그 작업의 세션만 후보다. 지목을 무시하고 다른 세션을
|
|
42
|
+
// 이어가면, 물어본 것과 다른 일이 시작된다 (실제로 그렇게 됐다).
|
|
43
|
+
const candidates = intent.workRef
|
|
44
|
+
? runnable.filter((session) => session.goal.includes(intent.workRef))
|
|
45
|
+
: runnable;
|
|
37
46
|
if (candidates.length === 0) {
|
|
47
|
+
if (intent.workRef && this.#ingress)
|
|
48
|
+
return this.#ingest(intent.workRef, intent.goal, this.#ingress);
|
|
38
49
|
// 자동 issue 금지. 초안을 제안할 수는 있으나 발급은 Controller 승인 후 별도 행위다.
|
|
39
50
|
return {
|
|
40
51
|
kind: 'PROPOSE_CONTRACT',
|
|
@@ -56,6 +67,80 @@ export class Operator {
|
|
|
56
67
|
}
|
|
57
68
|
return this.#advance(candidates[0]);
|
|
58
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* 돌릴 세션은 없고 사람이 작업 항목을 지목했다 (P0-D).
|
|
72
|
+
*
|
|
73
|
+
* 순서에 뜻이 있다: **조사 → 실제 상태 판정 → (필요하면) 계약**. 계약부터 만들면 이미
|
|
74
|
+
* 끝난 일에 세션을 내게 되고, 그것이 이 경로를 고치게 된 사고였다.
|
|
75
|
+
*/
|
|
76
|
+
async #ingest(workRef, goal, ingress) {
|
|
77
|
+
const gathered = await ingress.gather(workRef);
|
|
78
|
+
// 작업 항목이 지목한 경로를 함께 확인한다 — 그것이 좁은 쓰기 범위의 유일한 근거다.
|
|
79
|
+
const hints = gathered.workItem ? extractPathHints(gathered.workItem) : [];
|
|
80
|
+
const repo = ingress.observeRepo
|
|
81
|
+
? await ingress.observeRepo({ refHint: workRef, ...(hints.length > 0 ? { paths: hints } : {}) })
|
|
82
|
+
: 'MISSING';
|
|
83
|
+
const workState = judgeWorkState({ ...gathered, repo });
|
|
84
|
+
const decided = workState.state === 'DECIDABLE_WITH_LIMITATION' ? workState.leaning : workState.state;
|
|
85
|
+
if (decided !== 'ACTIONABLE' || !gathered.workItem) {
|
|
86
|
+
return { kind: 'WORK_STATE', workRef, result: workState, nextAction: nextActionFor(workState, workRef) };
|
|
87
|
+
}
|
|
88
|
+
const draft = ingress.derive({
|
|
89
|
+
workRef,
|
|
90
|
+
goal,
|
|
91
|
+
workItem: gathered.workItem,
|
|
92
|
+
workState,
|
|
93
|
+
repo,
|
|
94
|
+
// 이미 쓴 id 목록은 통로가 안다 — 보관된 것까지 세는 것이 그쪽이다.
|
|
95
|
+
existingIds: await ingress.usedIds(),
|
|
96
|
+
});
|
|
97
|
+
const plan = await ingress.plan(draft);
|
|
98
|
+
const handout = {
|
|
99
|
+
kind: 'PROPOSE_CONTRACT',
|
|
100
|
+
draft: {
|
|
101
|
+
role: (draft.role ?? 'implementer'),
|
|
102
|
+
goal: draft.goal ?? '',
|
|
103
|
+
doneCriteria: [...(draft.criteria ?? [])],
|
|
104
|
+
},
|
|
105
|
+
full: draft,
|
|
106
|
+
plan,
|
|
107
|
+
};
|
|
108
|
+
// 계약이 성립하지 않으면 발급하지 않는다. 무엇이 남았는지는 plan 이 이미 말한다.
|
|
109
|
+
if (plan.status !== 'READY_TO_ISSUE')
|
|
110
|
+
return handout;
|
|
111
|
+
// 발급 권한은 사람의 것이다. 위임이 없으면 명령만 건네고 멈춘다 (OM §450).
|
|
112
|
+
if (plan.issuance.authority !== 'delegated') {
|
|
113
|
+
return { ...handout, forController: issueCommand(draft) };
|
|
114
|
+
}
|
|
115
|
+
// 백스톱: 저장소 전체를 쓰겠다는 계약은 위임 범위 안에서 스스로 내지 않는다.
|
|
116
|
+
// 역할의 상한이 `**` 라는 사실은 **이번 작업이** 전체를 고쳐야 한다는 근거가 아니다 —
|
|
117
|
+
// 그 근거는 사람이 이 작업에 대해 직접 말했을 때만 성립한다.
|
|
118
|
+
if (isRepositoryWide(draft) && !declaredRepositoryWide(draft)) {
|
|
119
|
+
return {
|
|
120
|
+
...handout,
|
|
121
|
+
forController: issueCommand(draft),
|
|
122
|
+
plan: {
|
|
123
|
+
...plan,
|
|
124
|
+
unresolved: [
|
|
125
|
+
...plan.unresolved,
|
|
126
|
+
{
|
|
127
|
+
field: 'boundary',
|
|
128
|
+
reason: 'explicit_rule_requires_approval',
|
|
129
|
+
detail: '저장소 전체를 쓰는 계약이다 — 이번 작업이 그것을 요구한다는 근거가 없어 위임 범위 안에서 발급하지 않는다.',
|
|
130
|
+
},
|
|
131
|
+
],
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
const issued = await ingress.issue(draft);
|
|
136
|
+
if (!issued.ok)
|
|
137
|
+
return { kind: 'FAILED', reason: 'TRANSITION', detail: issued.detail };
|
|
138
|
+
const session = await this.#store.get('session', issued.sessionId);
|
|
139
|
+
if (!session) {
|
|
140
|
+
return { kind: 'FAILED', reason: 'NOT_FOUND', detail: `발급한 세션 '${issued.sessionId}' 을 찾지 못했다` };
|
|
141
|
+
}
|
|
142
|
+
return this.#advance(session);
|
|
143
|
+
}
|
|
59
144
|
/** 상태별로 한 걸음. 전이는 전부 SessionRuntime을 지난다 — 우회 경로 없음. */
|
|
60
145
|
async #advance(session) {
|
|
61
146
|
switch (session.status) {
|
|
@@ -165,3 +250,40 @@ function handout(session) {
|
|
|
165
250
|
...(session.checkpoint ? { checkpoint: session.checkpoint } : {}),
|
|
166
251
|
};
|
|
167
252
|
}
|
|
253
|
+
/** 세션을 내지 않을 때, 대신 무엇을 하면 되는가. 실행하지 않고 말만 한다. */
|
|
254
|
+
function nextActionFor(result, workRef) {
|
|
255
|
+
switch (result.state === 'DECIDABLE_WITH_LIMITATION' ? (result.leaning ?? 'UNDECIDABLE') : result.state) {
|
|
256
|
+
case 'IMPLEMENTED_STALE_TRACKER':
|
|
257
|
+
return `구현은 정본에 있다 — 할 일은 구현이 아니라 ${workRef} 상태 정리다. 추적 시스템 반영은 외부 쓰기이므로 승인 경로(Grant)를 지난다`;
|
|
258
|
+
case 'IMPLEMENTATION_COMPLETE_BLOCKED_VERIFICATION':
|
|
259
|
+
return '구현은 끝났고 검증이 막혀 있다 — 막힌 것을 먼저 풀어라. 새 구현 세션은 필요 없다';
|
|
260
|
+
case 'BLOCKED_DEPENDENCY':
|
|
261
|
+
return '선행 작업이 열려 있다 — 그것이 닫히기 전에는 이 작업만으로 끝나지 않는다';
|
|
262
|
+
case 'REVIEW_RESPONSE_REQUIRED':
|
|
263
|
+
return '검토가 답을 기다린다 — 다음 행동은 새 구현이 아니라 응답이다';
|
|
264
|
+
default:
|
|
265
|
+
return `결론 요건이 모자라다: ${result.missing.join(', ') || '확인하지 못한 것이 있다'} — 그것을 먼저 확인하라`;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
/** Controller 가 그대로 실행할 발급 명령. 조립만 하고 실행하지 않는다. */
|
|
269
|
+
function issueCommand(draft) {
|
|
270
|
+
const argv = ['asc', 'session', 'issue', draft.id ?? '<S-ID>', '--role', draft.role ?? 'implementer'];
|
|
271
|
+
if (draft.goal)
|
|
272
|
+
argv.push('--goal', draft.goal);
|
|
273
|
+
for (const scope of draft.boundary ?? [])
|
|
274
|
+
argv.push('--boundary', scope);
|
|
275
|
+
for (const criterion of draft.criteria ?? [])
|
|
276
|
+
argv.push('--criteria', criterion);
|
|
277
|
+
return argv;
|
|
278
|
+
}
|
|
279
|
+
/** 사실상 저장소 전체를 쓰는 범위인가. 문법 판정이 아니라 리터럴 확인이다. */
|
|
280
|
+
function isRepositoryWide(draft) {
|
|
281
|
+
return (draft.boundary ?? []).some((scope) => scope.replace(/\*.*$/, '') === '');
|
|
282
|
+
}
|
|
283
|
+
/** 이번 작업에 대해 사람·정본·Profile 이 그 범위를 직접 말했는가. */
|
|
284
|
+
function declaredRepositoryWide(draft) {
|
|
285
|
+
const entry = draft.provenance?.find((field) => field.field === 'boundary');
|
|
286
|
+
if (!entry || entry.status !== 'FACT')
|
|
287
|
+
return false;
|
|
288
|
+
return entry.source === 'user' || entry.source === 'profile' || entry.source === 'canonical';
|
|
289
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { ChangeSummary } from '../../ports/change-context.ts';
|
|
2
|
+
import type { RepoObservation } from '../../ports/local-repo.ts';
|
|
3
|
+
import type { ContextComment, ResourceSnapshot } from '../../ports/resource-context.ts';
|
|
4
|
+
export type WorkState =
|
|
5
|
+
/** 지금 착수할 수 있다. */
|
|
6
|
+
'ACTIONABLE'
|
|
7
|
+
/** 구현은 정본에 있는데 tracker 가 따라오지 않았다 — 할 일은 상태 정리지 구현이 아니다. */
|
|
8
|
+
| 'IMPLEMENTED_STALE_TRACKER'
|
|
9
|
+
/** 구현은 끝났고 남은 것은 검증인데, 그 검증이 외부 사정으로 막혀 있다. */
|
|
10
|
+
| 'IMPLEMENTATION_COMPLETE_BLOCKED_VERIFICATION'
|
|
11
|
+
/** 선행 작업이 열려 있어 이 작업만으로 끝낼 수 없다. */
|
|
12
|
+
| 'BLOCKED_DEPENDENCY'
|
|
13
|
+
/** 남의 검토에 답하는 것이 다음 행동이다. */
|
|
14
|
+
| 'REVIEW_RESPONSE_REQUIRED'
|
|
15
|
+
/** 못 본 것이 있지만 판정을 뒤집을 정도는 아니다 — 무엇에 기울었는지 `leaning` 이 말한다. */
|
|
16
|
+
| 'DECIDABLE_WITH_LIMITATION'
|
|
17
|
+
/** 결론 요건이 채워지지 않았다. 추천하지 않는다. */
|
|
18
|
+
| 'UNDECIDABLE';
|
|
19
|
+
/** 결정을 내릴 수 있는 상태들 — DECIDABLE_WITH_LIMITATION 의 `leaning` 이 가리키는 값. */
|
|
20
|
+
export type DecidedState = Exclude<WorkState, 'DECIDABLE_WITH_LIMITATION' | 'UNDECIDABLE'>;
|
|
21
|
+
/**
|
|
22
|
+
* 보지 못한 것을 두 가지로 나눠 받는다. `'UNAVAILABLE'` 은 보려 했으나 막힌 것이고,
|
|
23
|
+
* 필드 자체가 없으면 이 사건에 해당하지 않는 것이다. **저장소는 예외다** —
|
|
24
|
+
* `'MISSING'`(아예 안 봤다)이면 어떤 추천도 하지 않는다.
|
|
25
|
+
*/
|
|
26
|
+
export type WorkStateInput = {
|
|
27
|
+
workItem?: ResourceSnapshot;
|
|
28
|
+
/**
|
|
29
|
+
* tracker 가 "끝났다"고 말하는가. provider 어휘(한국어 상태명 등) 해석은 호출측 몫이다 —
|
|
30
|
+
* Core 가 특정 tracker 의 상태 문자열을 알면 다른 tracker 를 붙일 수 없다. 모르면 undefined.
|
|
31
|
+
*/
|
|
32
|
+
trackerDone?: boolean;
|
|
33
|
+
comments?: readonly ContextComment[] | 'UNAVAILABLE';
|
|
34
|
+
/** 저장소 관측. `'MISSING'` 은 "조사하지 않았다" 이며 결론 요건 미달이다. */
|
|
35
|
+
repo: RepoObservation | 'MISSING';
|
|
36
|
+
/** MR/PR 검토 상태. 원격 provider 가 막혀 있으면 `'UNAVAILABLE'`. */
|
|
37
|
+
change?: ChangeSummary | 'UNAVAILABLE';
|
|
38
|
+
dependencies?: readonly {
|
|
39
|
+
reference: string;
|
|
40
|
+
state?: string;
|
|
41
|
+
open?: boolean;
|
|
42
|
+
}[];
|
|
43
|
+
};
|
|
44
|
+
export type WorkStateResult = {
|
|
45
|
+
state: WorkState;
|
|
46
|
+
leaning?: DecidedState;
|
|
47
|
+
/** 판정의 근거. 사람이 그대로 읽는다. */
|
|
48
|
+
evidence: string[];
|
|
49
|
+
/** 보려 했으나 못 본 것. 판정을 뒤집지는 않지만 숨기지도 않는다. */
|
|
50
|
+
limitations: string[];
|
|
51
|
+
/** 결론 요건 중 비어 있는 칸. 비어 있지 않으면 UNDECIDABLE 이다. */
|
|
52
|
+
missing: string[];
|
|
53
|
+
};
|
|
54
|
+
export declare function judgeWorkState(input: WorkStateInput): WorkStateResult;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
// 실제 작업 상태 판정 (P0-B).
|
|
2
|
+
//
|
|
3
|
+
// 존재 이유는 한 문장이다. **tracker 상태는 실제 작업 상태가 아니다.** 추적 항목이 "진행 중"
|
|
4
|
+
// 이라고 말할 때 구현은 이미 정본 가지에 병합돼 있을 수 있고, "해야 할 일"인 항목이
|
|
5
|
+
// 자격증명 대기로 아무도 손댈 수 없는 상태일 수 있다. 그 차이를 tracker 한 곳만 보고
|
|
6
|
+
// 판단하면 이미 끝난 일을 다시 시키는 추천이 나간다.
|
|
7
|
+
//
|
|
8
|
+
// 이 모듈은 순수 판정이다. 읽지 않고, 쓰지 않고, 어떤 Port 도 부르지 않는다. 증거는
|
|
9
|
+
// 호출측이 모아서 넘긴다 — 무엇을 못 모았는지까지 함께.
|
|
10
|
+
//
|
|
11
|
+
// 여기서 만든 상태는 **파생 뷰**다. 어디에도 저장하지 않고 Session/Request 상태 enum
|
|
12
|
+
// (OM §11.2, 동결)을 건드리지 않는다.
|
|
13
|
+
/**
|
|
14
|
+
* 결론 `ACTIONABILITY_RECOMMENDATION` 의 필수 증거.
|
|
15
|
+
*
|
|
16
|
+
* 원격 API 가 막혀 있다는 사실은 저장소를 안 본 사유가 되지 않는다 — ref·병합·파일 존재는
|
|
17
|
+
* 로컬 git 으로 읽힌다. 그래서 repository 는 'UNAVAILABLE' 이라는 선택지 없이 필수다.
|
|
18
|
+
*/
|
|
19
|
+
const REQUIRED = ['work-item', 'repository'];
|
|
20
|
+
export function judgeWorkState(input) {
|
|
21
|
+
const evidence = [];
|
|
22
|
+
const limitations = [];
|
|
23
|
+
const missing = [];
|
|
24
|
+
if (!input.workItem || input.workItem.missing)
|
|
25
|
+
missing.push(REQUIRED[0]);
|
|
26
|
+
if (input.repo === 'MISSING')
|
|
27
|
+
missing.push(REQUIRED[1]);
|
|
28
|
+
else if (input.repo.unavailable)
|
|
29
|
+
limitations.push(`저장소 관측 실패: ${input.repo.unavailable}`);
|
|
30
|
+
if (missing.length > 0) {
|
|
31
|
+
return {
|
|
32
|
+
state: 'UNDECIDABLE',
|
|
33
|
+
evidence,
|
|
34
|
+
limitations,
|
|
35
|
+
missing,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
const repo = input.repo;
|
|
39
|
+
const item = input.workItem;
|
|
40
|
+
evidence.push(`작업 항목: ${item.reference} — ${item.title} (${item.state})`);
|
|
41
|
+
if (input.trackerDone !== undefined)
|
|
42
|
+
evidence.push(`tracker 완료 표기: ${input.trackerDone ? '예' : '아니오'}`);
|
|
43
|
+
if (input.comments === 'UNAVAILABLE')
|
|
44
|
+
limitations.push('논의를 읽지 못했다 — 합의 여부는 이 판정의 근거가 아니다');
|
|
45
|
+
if (input.change === 'UNAVAILABLE')
|
|
46
|
+
limitations.push('MR·리뷰 상태를 읽지 못했다 (원격 provider 미인증/불가)');
|
|
47
|
+
const onCanonical = Object.entries(repo.pathsOnCanonical ?? {}).filter(([, exists]) => exists);
|
|
48
|
+
const mentioned = repo.mentionedOnCanonical ?? [];
|
|
49
|
+
// 언급은 그 자체로 증거가 아니다. 되돌리기만 있는 이력도 이 작업을 "언급"하고, 뒤이어
|
|
50
|
+
// 걷혀 나간 변경도 그렇다. 살아남은 것이 있어야 정본에 있다고 말할 수 있다.
|
|
51
|
+
const mentionSurvives = mentioned.length > 0 && repo.mentionedOnlyReverts !== true && repo.mentionedArtifactsPresent === true;
|
|
52
|
+
const merged = repo.mergedIntoCanonical === true || onCanonical.length > 0 || mentionSurvives;
|
|
53
|
+
const hasBranch = repo.refs.length > 0;
|
|
54
|
+
const artifacts = Object.entries(repo.pathsExist).filter(([, exists]) => exists);
|
|
55
|
+
if (repo.canonicalRef)
|
|
56
|
+
evidence.push(`정본 대조 기준: ${repo.canonicalRef}`);
|
|
57
|
+
if (hasBranch)
|
|
58
|
+
evidence.push(`작업 가지: ${repo.refs.join(', ')}`);
|
|
59
|
+
if (repo.mergedIntoCanonical === true)
|
|
60
|
+
evidence.push('작업 가지가 정본에 병합돼 있다');
|
|
61
|
+
if (onCanonical.length > 0)
|
|
62
|
+
evidence.push(`정본에 산출물이 있다: ${onCanonical.map(([p]) => p).join(', ')}`);
|
|
63
|
+
if (artifacts.length > 0)
|
|
64
|
+
evidence.push(`작업 트리 산출물: ${artifacts.map(([p]) => p).join(', ')}`);
|
|
65
|
+
if (mentioned.length > 0)
|
|
66
|
+
evidence.push(`정본 이력이 이 작업을 언급한다: ${mentioned.join(' / ')}`);
|
|
67
|
+
if (repo.mentionedOnlyReverts === true) {
|
|
68
|
+
evidence.push('그 언급은 전부 되돌리기다 — 구현이 정본에 남아 있다는 증거가 아니다');
|
|
69
|
+
}
|
|
70
|
+
if (repo.mentionedArtifactsPresent === true) {
|
|
71
|
+
evidence.push('그 변경이 건드린 파일이 정본에 아직 있다 (생존 증거 — 인수 조건 충족의 증명은 아니다)');
|
|
72
|
+
}
|
|
73
|
+
if (repo.mentionedArtifactsPresent === false) {
|
|
74
|
+
evidence.push('그 변경이 건드린 파일이 정본에 하나도 남아 있지 않다');
|
|
75
|
+
}
|
|
76
|
+
if (mentioned.length > 0 && repo.mentionedArtifactsPresent === undefined) {
|
|
77
|
+
limitations.push('언급된 커밋이 무엇을 건드렸는지 읽지 못했다 — 구현이 지금도 남아 있는지 확인하지 못했다');
|
|
78
|
+
}
|
|
79
|
+
const openDependencies = (input.dependencies ?? []).filter((d) => d.open === true);
|
|
80
|
+
if (openDependencies.length > 0) {
|
|
81
|
+
evidence.push(`열린 선행 작업: ${openDependencies.map((d) => d.reference).join(', ')}`);
|
|
82
|
+
}
|
|
83
|
+
const unknownDependencies = (input.dependencies ?? []).filter((d) => d.open === undefined);
|
|
84
|
+
if (unknownDependencies.length > 0) {
|
|
85
|
+
limitations.push(`선행 작업 상태를 확인하지 못했다: ${unknownDependencies.map((d) => d.reference).join(', ')}`);
|
|
86
|
+
}
|
|
87
|
+
const implemented = merged || artifacts.length > 0;
|
|
88
|
+
// ① 구현은 정본에 있는데 tracker 가 안 따라왔다. 여기서만 tracker 를 본다 — 그것도
|
|
89
|
+
// "끝났다고 말하지 않는다"는 사실로만. tracker 가 결론을 만드는 자리는 없다.
|
|
90
|
+
// 확정하려면 **살아 있는 산출물**이 있어야 한다: 병합 흔적만으로는 부분 병합·스캐폴드·
|
|
91
|
+
// 되돌리기를 가려낼 수 없다. 확인하지 못했으면 확정하지 않는다.
|
|
92
|
+
// 가지가 정본의 조상이라는 것은 그 커밋들이 지금 정본 이력에 그대로 있다는 뜻이라
|
|
93
|
+
// 그 자체가 생존 증거다. 언급(grep)만 있는 경우와 다르다.
|
|
94
|
+
const artifactSurvives = repo.mergedIntoCanonical === true || onCanonical.length > 0 || repo.mentionedArtifactsPresent === true;
|
|
95
|
+
if (merged && input.trackerDone === false && artifactSurvives && repo.mentionedOnlyReverts !== true) {
|
|
96
|
+
limitations.push('인수 조건 전체가 지금도 충족되는지는 확인하지 않았다 — 여기서 말하는 것은 구현의 생존까지다');
|
|
97
|
+
return decided('IMPLEMENTED_STALE_TRACKER', evidence, limitations, { demote: false });
|
|
98
|
+
}
|
|
99
|
+
if (merged && input.trackerDone === false) {
|
|
100
|
+
// 병합 흔적은 있는데 생존을 확인하지 못했다 — 새 구현을 시키지도, 끝났다고 하지도 않는다.
|
|
101
|
+
limitations.push('정본에 병합 흔적은 있으나 구현이 지금도 남아 있는지 확인하지 못했다');
|
|
102
|
+
return decided('IMPLEMENTATION_COMPLETE_BLOCKED_VERIFICATION', evidence, limitations, { demote: true });
|
|
103
|
+
}
|
|
104
|
+
// ② 구현 증거는 있는데 남은 검증 경로가 막혔다.
|
|
105
|
+
if (implemented && input.change === 'UNAVAILABLE' && !merged) {
|
|
106
|
+
return decided('IMPLEMENTATION_COMPLETE_BLOCKED_VERIFICATION', evidence, limitations, { demote: true });
|
|
107
|
+
}
|
|
108
|
+
// ③ 가지는 있는데 병합 전이고 선행 작업이 열려 있다.
|
|
109
|
+
if (hasBranch && !merged && openDependencies.length > 0) {
|
|
110
|
+
return decided('BLOCKED_DEPENDENCY', evidence, limitations, { demote: false });
|
|
111
|
+
}
|
|
112
|
+
if (!implemented && openDependencies.length > 0) {
|
|
113
|
+
return decided('BLOCKED_DEPENDENCY', evidence, limitations, { demote: false });
|
|
114
|
+
}
|
|
115
|
+
// ④ 검토가 답을 기다린다.
|
|
116
|
+
if (typeof input.change === 'object' && requestsResponse(input.change, input.comments)) {
|
|
117
|
+
return decided('REVIEW_RESPONSE_REQUIRED', evidence, limitations, { demote: true });
|
|
118
|
+
}
|
|
119
|
+
// ⑤ 구현 증거도 없고 막힌 것도 없다.
|
|
120
|
+
if (!implemented) {
|
|
121
|
+
evidence.push('정본·작업 트리 어디에도 구현 증거가 없다');
|
|
122
|
+
return decided('ACTIONABLE', evidence, limitations, { demote: true });
|
|
123
|
+
}
|
|
124
|
+
// 구현 증거는 있는데 위 어디에도 안 걸린다 — 남은 것은 검증이고, 무엇이 막혔는지는 모른다.
|
|
125
|
+
return decided('IMPLEMENTATION_COMPLETE_BLOCKED_VERIFICATION', evidence, limitations, { demote: true });
|
|
126
|
+
}
|
|
127
|
+
/** 검토가 응답을 요구하는가. provider 어휘를 해석하지 않고 두 가지 표시만 본다. */
|
|
128
|
+
function requestsResponse(change, comments) {
|
|
129
|
+
const state = change.reviewState?.toUpperCase() ?? '';
|
|
130
|
+
if (state.includes('CHANGES_REQUESTED') || state.includes('REQUESTED_CHANGES'))
|
|
131
|
+
return true;
|
|
132
|
+
if (comments && comments !== 'UNAVAILABLE')
|
|
133
|
+
return comments.some((c) => c.unresolved === true);
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* 한계가 있으면 DECIDABLE_WITH_LIMITATION 으로 내리되, 판정의 근거가 로컬 사실뿐인
|
|
138
|
+
* 상태(①③)는 내리지 않는다 — 못 본 원격 정보가 그 결론을 바꾸지 못하기 때문이다.
|
|
139
|
+
*/
|
|
140
|
+
function decided(state, evidence, limitations, options) {
|
|
141
|
+
if (options.demote && limitations.length > 0) {
|
|
142
|
+
return { state: 'DECIDABLE_WITH_LIMITATION', leaning: state, evidence, limitations, missing: [] };
|
|
143
|
+
}
|
|
144
|
+
return { state, evidence, limitations, missing: [] };
|
|
145
|
+
}
|
|
@@ -18,14 +18,14 @@ export declare const ClosureRecord: z.ZodObject<{
|
|
|
18
18
|
/** 전부 확인된 시각. 닫혀도 기록은 남는다 — 지우면 무엇을 닫았는지도 사라진다. */
|
|
19
19
|
closedAt: z.ZodOptional<z.ZodString>;
|
|
20
20
|
}, "strip", z.ZodTypeAny, {
|
|
21
|
-
logicalSessionId: string;
|
|
22
21
|
openedAt: string;
|
|
22
|
+
logicalSessionId: string;
|
|
23
23
|
declared: string[];
|
|
24
24
|
confirmed: string[];
|
|
25
25
|
closedAt?: string | undefined;
|
|
26
26
|
}, {
|
|
27
|
-
logicalSessionId: string;
|
|
28
27
|
openedAt: string;
|
|
28
|
+
logicalSessionId: string;
|
|
29
29
|
declared?: string[] | undefined;
|
|
30
30
|
confirmed?: string[] | undefined;
|
|
31
31
|
closedAt?: string | undefined;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export type RepoObservation = {
|
|
2
|
+
/** 현재 체크아웃된 가지. 알 수 없으면 null. */
|
|
3
|
+
branch: string | null;
|
|
4
|
+
remotes: readonly {
|
|
5
|
+
name: string;
|
|
6
|
+
url: string;
|
|
7
|
+
}[];
|
|
8
|
+
/** refHint 에 걸린 지역·원격 ref 들 (예: 이슈 키가 들어간 작업 가지). */
|
|
9
|
+
refs: readonly string[];
|
|
10
|
+
/** 무엇을 정본으로 삼아 비교했는가. Profile 이 정한다 — adapter 가 추측하지 않는다. */
|
|
11
|
+
canonicalRef?: string;
|
|
12
|
+
/**
|
|
13
|
+
* refs 중 하나라도 정본 가지에 들어가 있는가. 판정이 아니라 **증거**다 —
|
|
14
|
+
* squash 병합이면 ref 는 조상이 아니지만 산출물은 정본에 있다. 그 경우
|
|
15
|
+
* `pathsOnCanonical` 이 같은 사실을 다른 각도로 말한다.
|
|
16
|
+
*/
|
|
17
|
+
mergedIntoCanonical?: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* 작업 트리에 그 경로가 있는가. **이 작업의 산출물 후보만** 담는다 — 저장소에 늘 있는
|
|
20
|
+
* 자리를 여기 섞으면 "구현이 있다"는 증거가 아무 작업에서나 성립해 버린다.
|
|
21
|
+
*/
|
|
22
|
+
pathsExist: Record<string, boolean>;
|
|
23
|
+
/**
|
|
24
|
+
* 저장소의 자리 목록(최상위 디렉터리 등). 범위를 좁힐 재료일 뿐 **증거가 아니다** —
|
|
25
|
+
* 판정은 이 값을 보지 않는다.
|
|
26
|
+
*/
|
|
27
|
+
modulesPresent?: Record<string, boolean>;
|
|
28
|
+
/** 정본 가지에 그 경로가 있는가 (squash 병합 대비). */
|
|
29
|
+
pathsOnCanonical?: Record<string, boolean>;
|
|
30
|
+
/**
|
|
31
|
+
* 정본 가지의 이력에서 이 작업을 언급하는 커밋들. **가지가 지워진 뒤에도 남는 증거다** —
|
|
32
|
+
* 병합 후 브랜치를 지우는 팀에서는 ref 대조만으로는 "구현이 정본에 있다"를 알 수 없다.
|
|
33
|
+
*/
|
|
34
|
+
mentionedOnCanonical?: readonly string[];
|
|
35
|
+
/**
|
|
36
|
+
* 언급 커밋이 전부 되돌리기인가. 되돌린 이력은 "구현이 정본에 있다"의 증거가 아니다.
|
|
37
|
+
*/
|
|
38
|
+
mentionedOnlyReverts?: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* 언급 커밋이 건드린 파일 중 하나라도 정본에 아직 살아 있는가.
|
|
41
|
+
*
|
|
42
|
+
* **이것은 생존 증거이지 인수 조건 충족의 증명이 아니다** — "그 변경이 지금 저장소와
|
|
43
|
+
* 끊어지지 않았다"까지만 말한다. 파일 목록을 읽지 못하면 undefined 로 둔다: 확인하지
|
|
44
|
+
* 않은 것을 확인했다고 하지 않는다.
|
|
45
|
+
*/
|
|
46
|
+
mentionedArtifactsPresent?: boolean;
|
|
47
|
+
/** git 자체를 쓸 수 없었던 이유. 있으면 이 관측은 비어 있다. */
|
|
48
|
+
unavailable?: string;
|
|
49
|
+
};
|
|
50
|
+
export type RepoQuery = {
|
|
51
|
+
/** ref 이름에서 찾을 조각. 보통 작업 항목 키. */
|
|
52
|
+
refHint?: string;
|
|
53
|
+
canonicalRef?: string;
|
|
54
|
+
paths?: readonly string[];
|
|
55
|
+
/** 범위 재료로만 확인할 자리들. 증거 칸에 섞이지 않는다. */
|
|
56
|
+
modulePaths?: readonly string[];
|
|
57
|
+
};
|
|
58
|
+
export interface LocalRepoPort {
|
|
59
|
+
readonly id: string;
|
|
60
|
+
observe(query: RepoQuery): Promise<RepoObservation>;
|
|
61
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Local Repository Port — 지금 이 저장소가 실제로 어떤 상태인가.
|
|
2
|
+
//
|
|
3
|
+
// 이 Port가 따로 있는 이유는 하나다. **원격 provider가 막혀 있다는 사실이 저장소를 보지
|
|
4
|
+
// 않을 사유가 되어서는 안 된다.** GitLab API가 401을 주든 토큰이 없든, ref·병합 여부·
|
|
5
|
+
// 파일 존재는 로컬 git 으로 전부 읽힌다. 그것을 안 읽고 "확인 불가"라고 말하면
|
|
6
|
+
// 조사 누락이 접근 불가로 위장된다.
|
|
7
|
+
//
|
|
8
|
+
// diff 도 로그도 다루지 않는다. "구현이 정본 가지에 있는가"를 판정할 최소한만 본다.
|
|
9
|
+
export {};
|
|
@@ -19,6 +19,13 @@ export type ResourceSnapshot = {
|
|
|
19
19
|
revisionMarker: string;
|
|
20
20
|
/** 연결된 다른 리소스. 문법은 adapter 소관이고 Core는 식별자로만 다룬다. */
|
|
21
21
|
related?: readonly string[];
|
|
22
|
+
/**
|
|
23
|
+
* 이 일을 **막는다고 선언된** 것들. `related` 의 부분집합이다.
|
|
24
|
+
*
|
|
25
|
+
* 따로 두는 이유: 부모·하위 작업도 연결이지만 선행이 아니다. 그것을 선행으로 세면
|
|
26
|
+
* 거의 모든 작업이 "막혔다"가 되고, 그 판정은 아무 말도 하지 않는 것과 같다.
|
|
27
|
+
*/
|
|
28
|
+
blockedBy?: readonly string[];
|
|
22
29
|
/** 사라졌거나 접근할 수 없다. 없는 것과 못 읽는 것을 구분해야 판정이 성립한다. */
|
|
23
30
|
missing?: boolean;
|
|
24
31
|
};
|
|
@@ -492,6 +492,7 @@ export declare const UserOverride: z.ZodPipeline<z.ZodEffects<z.ZodUnknown, unkn
|
|
|
492
492
|
roles?: string[] | undefined;
|
|
493
493
|
};
|
|
494
494
|
schemaVersion: 1;
|
|
495
|
+
monitorIdentities: string[];
|
|
495
496
|
approval: {
|
|
496
497
|
preferredChannel?: string | undefined;
|
|
497
498
|
messenger?: {
|
|
@@ -509,7 +510,6 @@ export declare const UserOverride: z.ZodPipeline<z.ZodEffects<z.ZodUnknown, unkn
|
|
|
509
510
|
unionLists?: Record<string, string[]> | undefined;
|
|
510
511
|
};
|
|
511
512
|
identity: Record<string, string>;
|
|
512
|
-
monitorIdentities: string[];
|
|
513
513
|
$comment?: string | undefined;
|
|
514
514
|
}, {
|
|
515
515
|
schemaVersion: 1;
|
|
@@ -521,6 +521,7 @@ export declare const UserOverride: z.ZodPipeline<z.ZodEffects<z.ZodUnknown, unkn
|
|
|
521
521
|
roles?: string[] | undefined;
|
|
522
522
|
} | undefined;
|
|
523
523
|
$comment?: string | undefined;
|
|
524
|
+
monitorIdentities?: string[] | undefined;
|
|
524
525
|
approval?: {
|
|
525
526
|
preferredChannel?: string | undefined;
|
|
526
527
|
messenger?: {
|
|
@@ -538,7 +539,6 @@ export declare const UserOverride: z.ZodPipeline<z.ZodEffects<z.ZodUnknown, unkn
|
|
|
538
539
|
unionLists?: Record<string, string[]> | undefined;
|
|
539
540
|
} | undefined;
|
|
540
541
|
identity?: Record<string, string> | undefined;
|
|
541
|
-
monitorIdentities?: string[] | undefined;
|
|
542
542
|
}>>;
|
|
543
543
|
export type UserOverride = z.infer<typeof UserOverride>;
|
|
544
544
|
/** attach 재현성 metadata (OM §4.9). Resolver만 쓴다. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@asc-agent/runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "ASC (Agent Session Control) — local-first human-in-the-loop agent control plane: core, CLI, and adapters",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"author": "colosair (https://github.com/colosair)",
|
|
23
23
|
"type": "module",
|
|
24
24
|
"bin": {
|
|
25
|
-
"asc": "
|
|
25
|
+
"asc": "dist/cli/asc.js"
|
|
26
26
|
},
|
|
27
27
|
"main": "./dist/cli/asc.js",
|
|
28
28
|
"types": "./dist/cli/asc.d.ts",
|