@asc-agent/runtime 0.4.0 → 0.5.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.
Files changed (38) hide show
  1. package/README.md +1 -1
  2. package/dist/adapters/fixture-surface/index.d.ts +33 -0
  3. package/dist/adapters/fixture-surface/index.js +97 -0
  4. package/dist/adapters/gitlab/adapter.js +1 -0
  5. package/dist/adapters/gitlab/client.d.ts +16 -2
  6. package/dist/adapters/gitlab/client.js +27 -0
  7. package/dist/adapters/gitlab/coordination.d.ts +19 -0
  8. package/dist/adapters/gitlab/coordination.js +111 -0
  9. package/dist/adapters/gitlab/ports.d.ts +6 -0
  10. package/dist/adapters/gitlab/ports.js +29 -2
  11. package/dist/adapters/local/canonical.d.ts +20 -0
  12. package/dist/adapters/local/canonical.js +58 -0
  13. package/dist/adapters/markdown/state-store.js +2 -1
  14. package/dist/adapters/service/launchd.js +18 -1
  15. package/dist/adapters/service/systemd-user.js +4 -1
  16. package/dist/adapters/webhook/ingress.d.ts +2 -2
  17. package/dist/cli/asc.js +244 -6
  18. package/dist/composition/runtime.d.ts +3 -0
  19. package/dist/composition/runtime.js +6 -1
  20. package/dist/core/binding/types.d.ts +1 -1
  21. package/dist/core/binding/types.js +1 -0
  22. package/dist/core/distribution/external-command.d.ts +26 -0
  23. package/dist/core/distribution/external-command.js +58 -1
  24. package/dist/core/distribution/persistent-runtime.d.ts +9 -0
  25. package/dist/core/distribution/release.d.ts +3 -3
  26. package/dist/core/distribution/release.js +1 -1
  27. package/dist/core/runtime/coordination.d.ts +312 -0
  28. package/dist/core/runtime/coordination.js +247 -0
  29. package/dist/core/runtime/front.d.ts +10 -0
  30. package/dist/core/runtime/front.js +16 -1
  31. package/dist/core/runtime/publish.d.ts +107 -0
  32. package/dist/core/runtime/publish.js +153 -0
  33. package/dist/core/runtime/workspaces.d.ts +22 -0
  34. package/dist/core/runtime/workspaces.js +18 -0
  35. package/dist/ports/coordination-surface.d.ts +59 -0
  36. package/dist/ports/coordination-surface.js +16 -0
  37. package/dist/ports/resource-context.d.ts +5 -0
  38. package/package.json +1 -1
@@ -0,0 +1,107 @@
1
+ import type { CoordinationSurfacePort, PublicPayload, SurfaceCandidate } from '../../ports/coordination-surface.ts';
2
+ import type { CoordinationLedger, CommunicationEvidence, RemoteIdentity } from './coordination.ts';
3
+ /**
4
+ * 게시 의도. **내부와 공개를 처음부터 다른 필드로 든다** (F4).
5
+ *
6
+ * 한 문자열에 담았다가 나중에 가르는 경로는 여기 없다. `internal` 은 이 타입 밖으로
7
+ * 나가지 않으며, Port 가 받는 것은 `publicPayload` 뿐이다.
8
+ */
9
+ export type PublishIntent = {
10
+ /** 어느 기대에 대한 게시인가. */
11
+ queryId: string;
12
+ /** 밖으로 나가는 내용 전부. */
13
+ publicPayload: PublicPayload;
14
+ /** 상관 관계·근거 출처·라우팅·사적 메모. **경계를 넘지 않는다.** */
15
+ internal?: Record<string, unknown>;
16
+ /** 누구에게 닿아야 하는가. 프로젝트 정책이 정한다. */
17
+ audience?: readonly string[];
18
+ /** 이미 아는 게시물. 있으면 찾기의 첫 근거가 된다. */
19
+ known?: readonly Pick<RemoteIdentity, 'objectType' | 'objectId'>[];
20
+ /** 관련 작업 항목. adapter 어휘 그대로. */
21
+ workReference?: string;
22
+ };
23
+ export type PublishDecision = {
24
+ verdict: 'ATTACH_EXISTING';
25
+ candidate: SurfaceCandidate;
26
+ why: string;
27
+ } | {
28
+ verdict: 'CREATE_NEW';
29
+ why: string;
30
+ } | {
31
+ verdict: 'AMBIGUOUS';
32
+ candidates: SurfaceCandidate[];
33
+ why: string;
34
+ };
35
+ /**
36
+ * 찾은 것들을 보고 무엇을 할지 정한다. **밖을 치지 않는다** — 판정만 한다.
37
+ *
38
+ * 강한 근거(이미 아는 신원 · 우리가 심은 상관 관계)가 정확히 하나면 그것에 붙는다.
39
+ * 강한 근거가 여럿이면 고르지 않는다. 약한 근거뿐이면 그것으로 붙지 않고, 그렇다고
40
+ * 새로 만들지도 않는다 — 같은 것이 이미 있을 수 있기 때문이다.
41
+ */
42
+ export declare function decidePublish(candidates: readonly SurfaceCandidate[]): PublishDecision;
43
+ export type PublishOutcome =
44
+ /** 새로 만들었고, 되읽어 확인했다. */
45
+ {
46
+ ok: true;
47
+ action: 'CREATED';
48
+ identity: RemoteIdentity;
49
+ evidence: Omit<CommunicationEvidence, 'evidenceId' | 'observedAt'>;
50
+ }
51
+ /** 이미 있던 것에 붙였다. 아무것도 만들지 않았다. */
52
+ | {
53
+ ok: true;
54
+ action: 'ATTACHED';
55
+ identity: RemoteIdentity;
56
+ evidence: Omit<CommunicationEvidence, 'evidenceId' | 'observedAt'>;
57
+ }
58
+ /** 사람이 정해야 한다. **만들지 않았다.** */
59
+ | {
60
+ ok: false;
61
+ reason: 'AMBIGUOUS';
62
+ candidates: SurfaceCandidate[];
63
+ detail: string;
64
+ }
65
+ /** 찾지 못했다 — 없는 것과 다르다. 이 상태에서 만들지 않는다. */
66
+ | {
67
+ ok: false;
68
+ reason: 'DISCOVERY_FAILED';
69
+ detail: string;
70
+ }
71
+ /** 만들었다는데 되읽히지 않는다. 다시 만들지 않는다. */
72
+ | {
73
+ ok: false;
74
+ reason: 'NOT_VERIFIED';
75
+ identity: RemoteIdentity;
76
+ detail: string;
77
+ } | {
78
+ ok: false;
79
+ reason: 'CREATE_FAILED';
80
+ detail: string;
81
+ };
82
+ export type PublishDeps = {
83
+ surface: CoordinationSurfacePort;
84
+ /** 이 binding 이 어떤 역할로 쓰이는가 (C-09). provider 이름이 아니다. */
85
+ bindingRole?: string;
86
+ now?: () => string;
87
+ };
88
+ /**
89
+ * 한 번 게시한다.
90
+ *
91
+ * **되읽기가 성공의 정본이다.** 만들기가 예외 없이 끝났다는 것만으로 끝내지 않는다 —
92
+ * 그 반대(만들어졌는데 응답을 잃음)가 실제로 일어났고, 그때 다시 만들면 중복이 된다.
93
+ * 되읽히지 않으면 실패로 적되 **다시 만들지 않는다**: 다음 회차가 찾기부터 다시 한다.
94
+ */
95
+ export declare function publishOnce(intent: PublishIntent, deps: PublishDeps): Promise<PublishOutcome>;
96
+ /**
97
+ * 게시 사실을 원장에 남긴다.
98
+ *
99
+ * `evidenceId` 를 **신원에서 만든다** — 같은 게시물을 다시 봐도 같은 id 가 나오고,
100
+ * 원장은 append-only 이므로 두 번째 기록은 조용히 거절된다 (F3). 무작위 id 를 쓰면
101
+ * 재시도할 때마다 같은 게시가 새 사실로 쌓인다.
102
+ */
103
+ export declare function recordPublication(ledger: CoordinationLedger, outcome: Extract<PublishOutcome, {
104
+ ok: true;
105
+ }>): Promise<Awaited<ReturnType<CoordinationLedger['publishRecorded']>>>;
106
+ /** 사람이 읽는 한 줄. 무엇을 했는지·무엇이 남았는지가 여기 있어야 한다. */
107
+ export declare function publishLine(outcome: PublishOutcome): string;
@@ -0,0 +1,153 @@
1
+ // Publish — 밖에 하나를 만들되, 두 번 만들지 않는다.
2
+ //
3
+ // 실 프로젝트에서 이런 일이 있었다:
4
+ //
5
+ // 생성 요청 → 원격에 실제로 생성됨 → 돌아온 주소 모양이 예상과 다름
6
+ // → 실패로 판정 → 다시 생성 → 같은 것이 둘
7
+ //
8
+ // 원인은 재시도가 아니라 **성공의 정본이 주소였다**는 것이다. 주소는 사람이 여는 값이고
9
+ // 시스템마다 모양이 다르며 바뀌기도 한다. 그것으로 존재를 판정하면 존재하지 않는 실패가
10
+ // 만들어진다.
11
+ //
12
+ // 그래서 순서를 고정한다:
13
+ //
14
+ // 찾는다 → 정한다 → (필요하면) 만든다 → 되읽는다 → 증거를 남긴다
15
+ //
16
+ // 그리고 세 판정 중 하나로만 끝난다:
17
+ //
18
+ // ATTACH_EXISTING 이미 있다. 만들지 않는다
19
+ // CREATE_NEW 없다. 만든다
20
+ // AMBIGUOUS 모르겠다. **만들지 않는다**
21
+ //
22
+ // 모호할 때 만드는 것이 중복을 만든다. 제목이 비슷하다는 것은 근거가 아니다 — 안정적인
23
+ // 관계 근거가 없으면 모르는 것이 맞고, 그 판단은 사람이 한다.
24
+ /**
25
+ * 찾은 것들을 보고 무엇을 할지 정한다. **밖을 치지 않는다** — 판정만 한다.
26
+ *
27
+ * 강한 근거(이미 아는 신원 · 우리가 심은 상관 관계)가 정확히 하나면 그것에 붙는다.
28
+ * 강한 근거가 여럿이면 고르지 않는다. 약한 근거뿐이면 그것으로 붙지 않고, 그렇다고
29
+ * 새로 만들지도 않는다 — 같은 것이 이미 있을 수 있기 때문이다.
30
+ */
31
+ export function decidePublish(candidates) {
32
+ const strong = candidates.filter((candidate) => candidate.matchedBy === 'known-identity' || candidate.matchedBy === 'correlation');
33
+ if (strong.length === 1) {
34
+ return {
35
+ verdict: 'ATTACH_EXISTING',
36
+ candidate: strong[0],
37
+ why: `이미 있다 (${strong[0].matchedBy})`,
38
+ };
39
+ }
40
+ if (strong.length > 1) {
41
+ return {
42
+ verdict: 'AMBIGUOUS',
43
+ candidates: strong,
44
+ why: `강한 근거를 가진 후보가 ${strong.length}개다 — 고르지 않는다`,
45
+ };
46
+ }
47
+ const weak = candidates.filter((candidate) => candidate.matchedBy === 'work-reference' || candidate.matchedBy === 'weak');
48
+ if (weak.length > 0) {
49
+ // 제목이나 작업 항목이 겹친다는 것은 같은 조율이라는 뜻이 아니다. 붙이지도, 만들지도
50
+ // 않는다 — 여기서 만들면 중복이 나고, 붙이면 남의 스레드에 끼어든다.
51
+ return {
52
+ verdict: 'AMBIGUOUS',
53
+ candidates: weak,
54
+ why: `약한 근거만 있다 (${weak.map((candidate) => candidate.matchedBy).join(', ')}) — 사람이 정한다`,
55
+ };
56
+ }
57
+ return { verdict: 'CREATE_NEW', why: '후보 없음' };
58
+ }
59
+ /**
60
+ * 한 번 게시한다.
61
+ *
62
+ * **되읽기가 성공의 정본이다.** 만들기가 예외 없이 끝났다는 것만으로 끝내지 않는다 —
63
+ * 그 반대(만들어졌는데 응답을 잃음)가 실제로 일어났고, 그때 다시 만들면 중복이 된다.
64
+ * 되읽히지 않으면 실패로 적되 **다시 만들지 않는다**: 다음 회차가 찾기부터 다시 한다.
65
+ */
66
+ export async function publishOnce(intent, deps) {
67
+ const now = deps.now ?? (() => new Date().toISOString());
68
+ const query = {
69
+ correlation: intent.queryId,
70
+ ...(intent.workReference ? { workReference: intent.workReference } : {}),
71
+ ...(intent.known ? { known: intent.known } : {}),
72
+ };
73
+ let candidates;
74
+ try {
75
+ candidates = await deps.surface.find(query);
76
+ }
77
+ catch (error) {
78
+ // 못 찾은 것을 "없다"로 읽으면 그 자리에서 중복이 난다.
79
+ return { ok: false, reason: 'DISCOVERY_FAILED', detail: String(error) };
80
+ }
81
+ const decision = decidePublish(candidates);
82
+ if (decision.verdict === 'AMBIGUOUS') {
83
+ return { ok: false, reason: 'AMBIGUOUS', candidates: decision.candidates, detail: decision.why };
84
+ }
85
+ const evidenceFor = (identity, source) => ({
86
+ queryId: intent.queryId,
87
+ ...(deps.bindingRole ? { bindingRole: deps.bindingRole } : {}),
88
+ identity,
89
+ audience: [...(intent.audience ?? [])],
90
+ publishedAt: now(),
91
+ evidenceSource: source,
92
+ });
93
+ if (decision.verdict === 'ATTACH_EXISTING') {
94
+ return {
95
+ ok: true,
96
+ action: 'ATTACHED',
97
+ identity: decision.candidate.identity,
98
+ evidence: evidenceFor(decision.candidate.identity, 'surface-discovered'),
99
+ };
100
+ }
101
+ let created;
102
+ try {
103
+ // **공개될 것만 넘어간다.** `intent.internal` 은 이 호출에 닿지 않는다 (F4).
104
+ created = await deps.surface.create(intent.publicPayload, query);
105
+ }
106
+ catch (error) {
107
+ return { ok: false, reason: 'CREATE_FAILED', detail: String(error) };
108
+ }
109
+ const snapshot = await deps.surface.read(created).catch(() => null);
110
+ if (!snapshot) {
111
+ return {
112
+ ok: false,
113
+ reason: 'NOT_VERIFIED',
114
+ identity: created,
115
+ detail: '만들었다고 했는데 되읽히지 않는다 — 다시 만들지 않는다. 다음 회차가 찾기부터 다시 한다.',
116
+ };
117
+ }
118
+ return {
119
+ ok: true,
120
+ action: 'CREATED',
121
+ identity: snapshot.identity,
122
+ evidence: evidenceFor(snapshot.identity, 'surface-read-back'),
123
+ };
124
+ }
125
+ /**
126
+ * 게시 사실을 원장에 남긴다.
127
+ *
128
+ * `evidenceId` 를 **신원에서 만든다** — 같은 게시물을 다시 봐도 같은 id 가 나오고,
129
+ * 원장은 append-only 이므로 두 번째 기록은 조용히 거절된다 (F3). 무작위 id 를 쓰면
130
+ * 재시도할 때마다 같은 게시가 새 사실로 쌓인다.
131
+ */
132
+ export async function recordPublication(ledger, outcome) {
133
+ const { identity } = outcome;
134
+ const evidenceId = `${outcome.evidence.queryId}:${identity.adapter}:${identity.objectType}:${identity.objectId}`;
135
+ return ledger.publishRecorded({ ...outcome.evidence, evidenceId });
136
+ }
137
+ /** 사람이 읽는 한 줄. 무엇을 했는지·무엇이 남았는지가 여기 있어야 한다. */
138
+ export function publishLine(outcome) {
139
+ if (outcome.ok) {
140
+ const at = outcome.identity.locator ?? `${outcome.identity.objectType}:${outcome.identity.objectId}`;
141
+ return outcome.action === 'CREATED' ? `published — ${at}` : `already there, attached — ${at}`;
142
+ }
143
+ switch (outcome.reason) {
144
+ case 'AMBIGUOUS':
145
+ return `not published — ${outcome.detail}. ${outcome.candidates.length} candidate(s); a person picks one.`;
146
+ case 'DISCOVERY_FAILED':
147
+ return `not published — could not look for an existing one (${outcome.detail}). Nothing was created.`;
148
+ case 'NOT_VERIFIED':
149
+ return `created but not verified — ${outcome.detail}`;
150
+ case 'CREATE_FAILED':
151
+ return `not published — ${outcome.detail}`;
152
+ }
153
+ }
@@ -37,3 +37,25 @@ export declare function dueWorkspaces(views: readonly WorkspaceView[]): {
37
37
  }[];
38
38
  /** 사람이 읽는 기계 전체 화면. `cd` 없이 지금 무엇이 도는지 보여야 한다 (설계 §13.2). */
39
39
  export declare function renderWorkspaces(views: readonly WorkspaceView[]): string[];
40
+ /** 회차 하나에서 workspace 하나가 어떻게 끝났는가. */
41
+ export type PassResult = {
42
+ workspaceId: string;
43
+ code: number;
44
+ };
45
+ /**
46
+ * 기계 회차 전체의 결과 (P0-R2).
47
+ *
48
+ * 원칙 둘이 같이 선다: **한 workspace 의 실패가 다른 workspace 를 세우지 않는다** — 그래서
49
+ * 전부 돌린 뒤에 센다. 그리고 **기계 수준 결과는 실패를 삼키지 않는다** — 하나라도 0 이 아니면
50
+ * 회차도 0 이 아니다. 예전에는 무조건 0 을 돌려줘서 OS 기록(LastExitStatus)에는 두 workspace 가
51
+ * 실패한 회차가 정상으로 남았다. 실패를 지운 것이 아니라 볼 수 없게 만든 것이고, 그것이 더 나쁘다.
52
+ */
53
+ export declare function summarizePass(ran: readonly PassResult[], skipped: readonly {
54
+ workspaceId: string;
55
+ health: WorkspaceHealth;
56
+ }[]): {
57
+ code: 0 | 1;
58
+ outcome: 'ok' | 'partial' | 'failed';
59
+ failed: PassResult[];
60
+ line: string;
61
+ };
@@ -62,3 +62,21 @@ export function renderWorkspaces(views) {
62
62
  }
63
63
  return lines;
64
64
  }
65
+ /**
66
+ * 기계 회차 전체의 결과 (P0-R2).
67
+ *
68
+ * 원칙 둘이 같이 선다: **한 workspace 의 실패가 다른 workspace 를 세우지 않는다** — 그래서
69
+ * 전부 돌린 뒤에 센다. 그리고 **기계 수준 결과는 실패를 삼키지 않는다** — 하나라도 0 이 아니면
70
+ * 회차도 0 이 아니다. 예전에는 무조건 0 을 돌려줘서 OS 기록(LastExitStatus)에는 두 workspace 가
71
+ * 실패한 회차가 정상으로 남았다. 실패를 지운 것이 아니라 볼 수 없게 만든 것이고, 그것이 더 나쁘다.
72
+ */
73
+ export function summarizePass(ran, skipped) {
74
+ const failed = ran.filter((result) => result.code !== 0);
75
+ const outcome = failed.length === 0 ? 'ok' : failed.length === ran.length ? 'failed' : 'partial';
76
+ const parts = [`${ran.length - failed.length} passed`];
77
+ if (failed.length > 0)
78
+ parts.push(`${failed.length} failed (${failed.map((r) => `${r.workspaceId}: ${r.code}`).join(', ')})`);
79
+ if (skipped.length > 0)
80
+ parts.push(`${skipped.length} skipped (${skipped.map((s) => s.health).join(', ')})`);
81
+ return { code: failed.length === 0 ? 0 : 1, outcome, failed, line: `Machine pass ${outcome} — ${parts.join(' · ')}` };
82
+ }
@@ -0,0 +1,59 @@
1
+ import type { RemoteIdentity } from '../core/runtime/coordination.ts';
2
+ /**
3
+ * 밖으로 나가는 내용 **전부**.
4
+ *
5
+ * 상관 관계·근거 출처·라우팅·사적 메모는 여기 없다. 그것들은 호출자 쪽 내부 값이며
6
+ * 이 타입으로 들어올 자리가 없다 — 그것이 이 분리의 요점이다.
7
+ */
8
+ export type PublicPayload = {
9
+ title: string;
10
+ body: string;
11
+ labels?: readonly string[];
12
+ };
13
+ /**
14
+ * 이미 있는 것을 찾기 위한 질의.
15
+ *
16
+ * **제목 유사도로 찾지 않는다.** 제목은 사람이 바꾸고, 비슷한 제목은 다른 것일 수 있다.
17
+ * 여기서 주는 것은 안정적인 관계 근거다 — 무엇에 대한 조율인지, 어떤 작업 항목에
18
+ * 걸려 있는지, 우리가 이미 아는 게시물이 있는지.
19
+ */
20
+ export type SurfaceQuery = {
21
+ /** 이 조율이 어느 기대에 대한 것인가. adapter 는 이것을 자기 방식으로 심을 수 있다. */
22
+ correlation: string;
23
+ /** 관련된 작업 항목의 신원. adapter 어휘 그대로이며 Core 는 해석하지 않는다. */
24
+ workReference?: string;
25
+ /** 이미 아는 게시물. 있으면 그것부터 확인한다. */
26
+ known?: readonly Pick<RemoteIdentity, 'objectType' | 'objectId'>[];
27
+ };
28
+ /** 찾아진 후보 하나. */
29
+ export type SurfaceCandidate = {
30
+ identity: RemoteIdentity;
31
+ title: string;
32
+ /**
33
+ * 왜 후보인가. **판정 근거를 값으로 든다** — 산문으로 적으면 호출자가 그것으로
34
+ * 판단할 수 없다.
35
+ */
36
+ matchedBy: 'known-identity' | 'correlation' | 'work-reference' | 'weak';
37
+ /** 닫힌 것인가. 닫힌 곳에 이어 붙일지는 호출자가 정한다. */
38
+ closed?: boolean;
39
+ };
40
+ /** 되읽기 결과. 없으면 `null` — "만들었다고 했는데 없다"는 별개의 사실이다. */
41
+ export type SurfaceSnapshot = {
42
+ identity: RemoteIdentity;
43
+ title: string;
44
+ closed?: boolean;
45
+ };
46
+ /**
47
+ * 조율 표면 하나. adapter 가 구현한다.
48
+ *
49
+ * Core 는 이 인터페이스만 알고 provider 를 모른다 (C-09 §6).
50
+ */
51
+ export interface CoordinationSurfacePort {
52
+ readonly id: string;
53
+ /** 이미 있는가. 없으면 빈 배열 — 못 찾은 것과 없는 것을 호출자가 구분하려면 throw 한다. */
54
+ find(query: SurfaceQuery): Promise<SurfaceCandidate[]>;
55
+ /** 만든다. 돌려주는 것은 주소가 아니라 **안정 신원**이다. */
56
+ create(payload: PublicPayload, query: SurfaceQuery): Promise<RemoteIdentity>;
57
+ /** 지금도 있는가. 만든 직후 이것으로 확인한다 — 만들었다는 주장만으로 끝내지 않는다. */
58
+ read(identity: Pick<RemoteIdentity, 'objectType' | 'objectId'>): Promise<SurfaceSnapshot | null>;
59
+ }
@@ -0,0 +1,16 @@
1
+ // Coordination Surface — 밖에 무언가를 게시하고, 그것을 다시 찾고, 다시 읽는 통로.
2
+ //
3
+ // 기존 `ScmPort.execute` 로는 이 일을 할 수 없다. 그쪽은 성공을 문자열 하나로 돌려주고
4
+ // (`resultRef`), 만들기 전에 이미 있는지 찾는 경로도, 만든 뒤 정말 있는지 되읽는 경로도
5
+ // 없다. 실 프로젝트에서 그 구조가 같은 것을 두 번 만들게 했다:
6
+ //
7
+ // 생성 성공 → 돌아온 주소 모양이 예상과 다름 → 실패로 판정 → 다시 생성
8
+ //
9
+ // 대조할 값이 계약에 없으면 그 판정을 막을 방법이 없다. 그래서 이 Port 는 세 가지를
10
+ // 요구한다 — **찾기 · 만들기 · 되읽기**. 그리고 성공의 정본은 주소가 아니라 그 시스템이
11
+ // 준 안정적인 id 다.
12
+ //
13
+ // **공개될 것만 지나간다.** `PublicPayload` 밖의 값은 이 경계에 올 수 없다 — 내부 메모와
14
+ // 공개 본문을 한 문자열에 담았다가 나중에 가르는 구조가 유출을 만들었고, 관례로 가르면
15
+ // 언젠가 갈리지 않는다 (F4).
16
+ export {};
@@ -6,6 +6,11 @@ export type ContextComment = {
6
6
  body: string;
7
7
  /** 아직 닫히지 않은 논의인가. provider가 알려주지 않으면 생략한다 — 추측하지 않는다. */
8
8
  unresolved?: boolean;
9
+ /**
10
+ * 사람이 쓴 것이 아니라 그 시스템이 남긴 자국인가 (커밋 언급·배정 변경 등).
11
+ * 사람의 답과 섞이면 "답이 왔다"가 자동으로 성립해 버린다.
12
+ */
13
+ system?: boolean;
9
14
  };
10
15
  export type ResourceSnapshot = {
11
16
  reference: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asc-agent/runtime",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "ASC (Agent Session Control) \u2014 local-first human-in-the-loop agent control plane: core, CLI, and adapters",
5
5
  "keywords": [
6
6
  "agent",