@cheongfish/bouncer 0.0.0-stage → 0.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/README.md +31 -2
- package/bin/bouncer +26 -0
- package/bin/bouncer-root +25 -0
- package/bin/build-stamp.js +78 -0
- package/bin/register-path.js +71 -0
- package/dist/.build-stamp +1 -0
- package/dist/approval-snapshot.js +99 -0
- package/dist/bouncer-root.js +80 -0
- package/dist/canonical-json.js +37 -0
- package/dist/cli-commit-guard-command.js +74 -0
- package/dist/cli-config-command.js +47 -0
- package/dist/cli-context-review-command.js +273 -0
- package/dist/cli-current-command.js +374 -0
- package/dist/cli-dispatch-command.js +119 -0
- package/dist/cli-doc-commands.js +352 -0
- package/dist/cli-epic-command.js +130 -0
- package/dist/cli-flags.js +50 -0
- package/dist/cli-git-commands.js +931 -0
- package/dist/cli-intent-command.js +491 -0
- package/dist/cli-metrics-command.js +134 -0
- package/dist/cli-ontology-command.js +109 -0
- package/dist/cli-project-commands.js +580 -0
- package/dist/cli-retro-command.js +138 -0
- package/dist/cli-review-command.js +251 -0
- package/dist/cli-review-dispatch-command.js +338 -0
- package/dist/cli-usage.js +72 -0
- package/dist/cli.js +70 -0
- package/dist/codex-agents.js +189 -0
- package/dist/command-argv.js +125 -0
- package/dist/commit-guard.js +72 -0
- package/dist/commit-hook.js +470 -0
- package/dist/commit-message.js +77 -0
- package/dist/commit-sha.js +107 -0
- package/dist/commit.js +519 -0
- package/dist/composite-gates.js +129 -0
- package/dist/comprehension.js +127 -0
- package/dist/config.js +298 -0
- package/dist/context-ids.js +288 -0
- package/dist/context-migration-rewrite.js +421 -0
- package/dist/context-ontology.js +746 -0
- package/dist/context-review-contracts.js +2 -0
- package/dist/context-review-projection.js +147 -0
- package/dist/context-review-state.js +824 -0
- package/dist/context-review-store.js +602 -0
- package/dist/context-review-transitions.js +412 -0
- package/dist/coordinate-advance.js +253 -0
- package/dist/coordinate-next.js +998 -0
- package/dist/coordinate-output.js +57 -0
- package/dist/coordinator-command-contracts.js +2 -0
- package/dist/coordinator-commands.js +1908 -0
- package/dist/coordinator-fanin.js +1021 -0
- package/dist/coordinator-git.js +121 -0
- package/dist/coordinator-input.js +37 -0
- package/dist/coordinator-ledger.js +381 -0
- package/dist/coordinator-prepare.js +1047 -0
- package/dist/coordinator-repair.js +1416 -0
- package/dist/coordinator-verification.js +42 -0
- package/dist/coordinator-worker.js +388 -0
- package/dist/coordinator.js +7 -0
- package/dist/cross-drive.js +85 -0
- package/dist/current.js +1148 -0
- package/dist/dag-shape.js +93 -0
- package/dist/doc-shape-contracts.js +241 -0
- package/dist/drive-metrics.js +691 -0
- package/dist/epic-index.js +347 -0
- package/dist/epic-registry.js +453 -0
- package/dist/execute-prepare.js +262 -0
- package/dist/execution-contracts.js +46 -0
- package/dist/execution-dag.js +148 -0
- package/dist/finalize-checks.js +177 -0
- package/dist/finalize-collect.js +343 -0
- package/dist/finalize-comprehension.js +116 -0
- package/dist/finalize-digest-collect.js +388 -0
- package/dist/finalize-digest-model.js +595 -0
- package/dist/finalize-digest.js +164 -0
- package/dist/finalize-explain.js +176 -0
- package/dist/finalize-pr.js +610 -0
- package/dist/finalize-release-main.js +652 -0
- package/dist/finalize.js +714 -0
- package/dist/frontmatter.js +22 -0
- package/dist/git-ops.js +49 -0
- package/dist/graph-exec.js +179 -0
- package/dist/graph-scope.js +268 -0
- package/dist/graphify.js +995 -0
- package/dist/guards.js +82 -0
- package/dist/import-git.js +78 -0
- package/dist/import-history.js +392 -0
- package/dist/import-render.js +59 -0
- package/dist/import-types.js +2 -0
- package/dist/init.js +571 -0
- package/dist/intent-bundle.js +936 -0
- package/dist/intent-provenance.js +927 -0
- package/dist/layout.js +131 -0
- package/dist/lease.js +145 -0
- package/dist/legacy-comments.js +71 -0
- package/dist/migrate-context-slugs.js +262 -0
- package/dist/migrate-merge-legacy-epics.js +392 -0
- package/dist/migrate-review-scope.js +144 -0
- package/dist/migrate-task-layout.js +194 -0
- package/dist/path-overlap.js +38 -0
- package/dist/paths.js +45 -0
- package/dist/plan-approve.js +218 -0
- package/dist/plan-inspect.js +575 -0
- package/dist/plan-prerequisites.js +197 -0
- package/dist/plan-snapshot-contract.js +98 -0
- package/dist/plan-snapshot.js +138 -0
- package/dist/plugin-root.js +237 -0
- package/dist/posix-path.js +14 -0
- package/dist/pre-commit-hook.js +170 -0
- package/dist/print-dispatch.js +285 -0
- package/dist/render.js +8 -0
- package/dist/retention-migration.js +595 -0
- package/dist/retro-read.js +259 -0
- package/dist/retro-sources.js +318 -0
- package/dist/review-dispatch.js +1054 -0
- package/dist/review-policy.js +28 -0
- package/dist/review-record.js +447 -0
- package/dist/review-repair-state.js +52 -0
- package/dist/review-snapshot.js +659 -0
- package/dist/run-detach.js +165 -0
- package/dist/run-preflight.js +368 -0
- package/dist/runtime-state.js +1038 -0
- package/dist/scaffold.js +533 -0
- package/dist/schema.js +294 -0
- package/dist/scope.js +1073 -0
- package/dist/seed-worktree.js +926 -0
- package/dist/session-graph.js +337 -0
- package/dist/subagents.js +86 -0
- package/dist/symbol-index.js +1390 -0
- package/dist/task-brief-hash.js +34 -0
- package/dist/task-commits.js +124 -0
- package/dist/tasks-docs.js +192 -0
- package/dist/templates.js +469 -0
- package/dist/time.js +21 -0
- package/dist/validate-docs.js +302 -0
- package/dist/validate-gates-finalize.js +303 -0
- package/dist/validate-gates-plan.js +720 -0
- package/dist/validate-gates-run.js +420 -0
- package/dist/validate-gates-shared.js +124 -0
- package/dist/validate-gates.js +87 -0
- package/dist/validate-sections.js +654 -0
- package/dist/validate-structural.js +347 -0
- package/dist/validate.js +417 -0
- package/dist/verification-contracts.js +26 -0
- package/dist/verification-evidence.js +233 -0
- package/dist/verification-execution.js +163 -0
- package/dist/verification-identity.js +413 -0
- package/dist/verification-target.js +51 -0
- package/dist/verification.js +383 -0
- package/dist/windows-npm.js +38 -0
- package/package.json +32 -4
- package/resources/graphify-compat.json +9 -0
- package/vendor/README.md +23 -0
- package/vendor/js-yaml.LICENSE +21 -0
- package/vendor/js-yaml.js +3022 -0
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.readReviewEvidence = readReviewEvidence;
|
|
4
|
+
exports.readVerification = readVerification;
|
|
5
|
+
exports.readReview = readReview;
|
|
6
|
+
exports.resolvePrBase = resolvePrBase;
|
|
7
|
+
exports.readDigestDocument = readDigestDocument;
|
|
8
|
+
exports.digestPathExists = digestPathExists;
|
|
9
|
+
exports.readBlueprintIndex = readBlueprintIndex;
|
|
10
|
+
exports.readTaskDocument = readTaskDocument;
|
|
11
|
+
exports.collectTaskDocuments = collectTaskDocuments;
|
|
12
|
+
exports.readDigestRegistry = readDigestRegistry;
|
|
13
|
+
exports.readDigestConfig = readDigestConfig;
|
|
14
|
+
exports.collectDigestGit = collectDigestGit;
|
|
15
|
+
exports.collectDigestStart = collectDigestStart;
|
|
16
|
+
const fs = require('node:fs');
|
|
17
|
+
const path = require('node:path');
|
|
18
|
+
const { spawnSync } = require('node:child_process');
|
|
19
|
+
const scope = require("./scope");
|
|
20
|
+
const { readCoordinatorLedger } = scope;
|
|
21
|
+
const tasksDocs = require("./tasks-docs");
|
|
22
|
+
const { listTasksDocs } = tasksDocs;
|
|
23
|
+
const currentMod = require("./current");
|
|
24
|
+
const { readCurrent } = currentMod;
|
|
25
|
+
const paths = require("./paths");
|
|
26
|
+
const { toPosix } = paths;
|
|
27
|
+
const frontmatter = require("./frontmatter");
|
|
28
|
+
const { readDoc } = frontmatter;
|
|
29
|
+
const configMod = require("./config");
|
|
30
|
+
const { readConfig } = configMod;
|
|
31
|
+
const registryMod = require("./epic-registry");
|
|
32
|
+
const { readEpicRegistry } = registryMod;
|
|
33
|
+
const taskCommits = require("./task-commits");
|
|
34
|
+
const { resolveTaskCommits } = taskCommits;
|
|
35
|
+
const comprehension = require("./comprehension");
|
|
36
|
+
const { computeDiffSha } = comprehension;
|
|
37
|
+
const gitOps = require("./git-ops");
|
|
38
|
+
const { resolveCheckoutBranch } = gitOps;
|
|
39
|
+
const schema = require("./schema");
|
|
40
|
+
const { executionKindOf } = schema;
|
|
41
|
+
function asRecord(value) {
|
|
42
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
43
|
+
? value
|
|
44
|
+
: {};
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* review frontmatter 파싱은 실패할 때만 null로 접어 기존 읽기 전용 계약을 유지한다.
|
|
48
|
+
*
|
|
49
|
+
* @param {string} absPath - 읽을 review.md 절대 경로
|
|
50
|
+
* @param {string} relPath - 결과에 기록할 상대 경로
|
|
51
|
+
* @returns {{ path: string, rounds: number, target_digest: string | null } | null}
|
|
52
|
+
*/
|
|
53
|
+
function readReviewEvidence(absPath, relPath) {
|
|
54
|
+
if (!fs.existsSync(absPath))
|
|
55
|
+
return null;
|
|
56
|
+
try {
|
|
57
|
+
const { data } = readDoc(absPath);
|
|
58
|
+
const review = asRecord(asRecord(asRecord(data).bouncer).review);
|
|
59
|
+
const rounds = Array.isArray(review.rounds) ? review.rounds : [];
|
|
60
|
+
const last = asRecord(rounds[rounds.length - 1]);
|
|
61
|
+
const perspective = asRecord(Array.isArray(last.perspectives) ? last.perspectives[0] : null);
|
|
62
|
+
const candidates = [last.target_digest, asRecord(last.target).digest, perspective.target_digest];
|
|
63
|
+
const digest = candidates.find((v) => typeof v === 'string' && v !== '');
|
|
64
|
+
return { path: relPath, rounds: rounds.length, target_digest: digest ?? null };
|
|
65
|
+
}
|
|
66
|
+
catch (_error) {
|
|
67
|
+
// YAML/frontmatter 파싱 실패는 리뷰 기록 없음과 같다 — digest가 throw하지 않는다.
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* verification frontmatter를 원문 로그 없이 요약한다.
|
|
73
|
+
*
|
|
74
|
+
* @param {string} absPath - 읽을 verification.md 절대 경로
|
|
75
|
+
* @returns {TaskDigest['verification']} 파일 부재·파싱 실패면 null
|
|
76
|
+
*/
|
|
77
|
+
function readVerification(absPath) {
|
|
78
|
+
if (!fs.existsSync(absPath))
|
|
79
|
+
return null;
|
|
80
|
+
try {
|
|
81
|
+
const { data } = readDoc(absPath);
|
|
82
|
+
const bouncer = asRecord(asRecord(data).bouncer);
|
|
83
|
+
const meta = asRecord(bouncer.verification);
|
|
84
|
+
return {
|
|
85
|
+
status: typeof bouncer.status === 'string' ? bouncer.status : 'unknown',
|
|
86
|
+
command: typeof meta.command === 'string' ? meta.command : null,
|
|
87
|
+
exit_code: typeof meta.exit_code === 'number' ? meta.exit_code : null,
|
|
88
|
+
evidence_id: typeof meta.evidence_id === 'string' ? meta.evidence_id : null,
|
|
89
|
+
reused: typeof meta.reused === 'boolean' ? meta.reused : null,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
catch (_error) {
|
|
93
|
+
// YAML/frontmatter 파싱 실패는 증적 없음과 같다 — digest가 문서 오류로 throw하지 않는다.
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* review frontmatter의 required·findings를 digest용으로 요약한다.
|
|
99
|
+
*
|
|
100
|
+
* @param {string | null} absPath - 읽을 review.md 절대 경로. 없으면 null
|
|
101
|
+
* @returns {TaskDigest['review']} 파일 부재·파싱 실패면 null
|
|
102
|
+
*/
|
|
103
|
+
function readReview(absPath) {
|
|
104
|
+
if (!absPath || !fs.existsSync(absPath))
|
|
105
|
+
return null;
|
|
106
|
+
try {
|
|
107
|
+
const { data } = readDoc(absPath);
|
|
108
|
+
const review = asRecord(asRecord(asRecord(data).bouncer).review);
|
|
109
|
+
const rawFindings = Array.isArray(review.findings) ? review.findings : [];
|
|
110
|
+
const findings = [];
|
|
111
|
+
for (const raw of rawFindings) {
|
|
112
|
+
const row = asRecord(raw);
|
|
113
|
+
if (typeof row.id !== 'string' || !row.id)
|
|
114
|
+
continue;
|
|
115
|
+
const finding = {
|
|
116
|
+
id: row.id,
|
|
117
|
+
severity: typeof row.severity === 'string' ? row.severity : 'nit',
|
|
118
|
+
status: typeof row.status === 'string' ? row.status : 'resolved',
|
|
119
|
+
};
|
|
120
|
+
if (typeof row.note === 'string' && row.note)
|
|
121
|
+
finding.note = row.note;
|
|
122
|
+
if (typeof row.actionability === 'string' && row.actionability) {
|
|
123
|
+
finding.actionability = row.actionability;
|
|
124
|
+
}
|
|
125
|
+
findings.push(finding);
|
|
126
|
+
}
|
|
127
|
+
return { required: review.required === false ? false : true, findings };
|
|
128
|
+
}
|
|
129
|
+
catch (_error) {
|
|
130
|
+
// YAML/frontmatter 오류는 유효한 finding을 만들 근거가 없으므로 리뷰 부재로 접는다.
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* PR base를 config 설정 또는 origin/HEAD에서 읽는다.
|
|
136
|
+
*
|
|
137
|
+
* @param {string} repoRoot - config를 읽을 checkout
|
|
138
|
+
* @param {GitExec} run - 주입 가능한 git 실행 seam
|
|
139
|
+
* @returns {string | null} 설정된 base, 없으면 null
|
|
140
|
+
*/
|
|
141
|
+
function resolvePrBase(repoRoot, run) {
|
|
142
|
+
const root = asRecord(readConfig(repoRoot));
|
|
143
|
+
const pr = asRecord(root.pr);
|
|
144
|
+
if (typeof pr.base === 'string' && pr.base.trim())
|
|
145
|
+
return pr.base.trim();
|
|
146
|
+
if (typeof root.base_branch === 'string' && root.base_branch.trim())
|
|
147
|
+
return root.base_branch.trim();
|
|
148
|
+
const result = run(['symbolic-ref', '--quiet', '--short', 'refs/remotes/origin/HEAD']);
|
|
149
|
+
if (result.status !== 0)
|
|
150
|
+
return null;
|
|
151
|
+
const ref = typeof result.stdout === 'string' ? result.stdout.trim() : '';
|
|
152
|
+
if (!ref.startsWith('origin/'))
|
|
153
|
+
return null;
|
|
154
|
+
return ref.slice('origin/'.length) || null;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* 단일 frontmatter 문서를 읽는다. 호출부가 실패 위치에 맞는 fallback을 적용한다.
|
|
158
|
+
*
|
|
159
|
+
* @param {string} absPath - 읽을 문서 절대 경로
|
|
160
|
+
* @returns {ReturnType<typeof readDoc>} frontmatter 데이터와 본문
|
|
161
|
+
*/
|
|
162
|
+
function readDigestDocument(absPath) {
|
|
163
|
+
return readDoc(absPath);
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* 수집 단계에서만 필요한 경로 존재 여부를 묻는다.
|
|
167
|
+
*
|
|
168
|
+
* @param {string} absPath - 확인할 절대 경로
|
|
169
|
+
* @returns {boolean} 경로가 존재하면 true
|
|
170
|
+
*/
|
|
171
|
+
function digestPathExists(absPath) {
|
|
172
|
+
return fs.existsSync(absPath);
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* blueprint index를 읽는다. 읽기·frontmatter 실패는 이전의 기본 메타데이터 경로를 유지한다.
|
|
176
|
+
*
|
|
177
|
+
* @param {string} absPath - blueprint index 절대 경로
|
|
178
|
+
* @returns {ReturnType<typeof readDoc> | null} 읽은 문서, 없거나 파싱 실패면 null
|
|
179
|
+
*/
|
|
180
|
+
function readBlueprintIndex(absPath) {
|
|
181
|
+
if (!fs.existsSync(absPath))
|
|
182
|
+
return null;
|
|
183
|
+
try {
|
|
184
|
+
return readDoc(absPath);
|
|
185
|
+
}
|
|
186
|
+
catch (_error) {
|
|
187
|
+
// blueprint index의 읽기·YAML 실패는 기존처럼 기본값으로 진행한다.
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* task 문서를 읽어 실패한 항목만 생략한다.
|
|
193
|
+
*
|
|
194
|
+
* @param {string} absPath - tasks.md 절대 경로
|
|
195
|
+
* @returns {ReturnType<typeof readDoc> | null} 읽은 문서, 없거나 파싱 실패면 null
|
|
196
|
+
*/
|
|
197
|
+
function readTaskDocument(absPath) {
|
|
198
|
+
if (!fs.existsSync(absPath))
|
|
199
|
+
return null;
|
|
200
|
+
try {
|
|
201
|
+
return readDoc(absPath);
|
|
202
|
+
}
|
|
203
|
+
catch (_error) {
|
|
204
|
+
// 손상 task 한 건만 생략해 나머지 digest를 보존한다.
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* listing의 tasks/verification/review 문서를 한 번씩 읽어 model용 snapshot으로 모은다.
|
|
210
|
+
*
|
|
211
|
+
* @param {object} opts - checkout와 document listing
|
|
212
|
+
* @param {string} opts.checkoutRoot - 문서를 읽을 checkout
|
|
213
|
+
* @param {Listing} opts.listing - task 문서 경로 목록
|
|
214
|
+
* @returns {Array<object>} 파싱 가능한 task와 각 보조 문서의 읽힌 값
|
|
215
|
+
*/
|
|
216
|
+
function collectTaskDocuments({ checkoutRoot, listing }) {
|
|
217
|
+
const snapshots = [];
|
|
218
|
+
for (const entry of listing.entries) {
|
|
219
|
+
const taskDoc = readTaskDocument(path.join(checkoutRoot, entry.tasks.rel));
|
|
220
|
+
if (!taskDoc)
|
|
221
|
+
continue;
|
|
222
|
+
const data = taskDoc.data;
|
|
223
|
+
const bouncer = asRecord(asRecord(data).bouncer);
|
|
224
|
+
const kind = executionKindOf(bouncer) || entry.executionKind || 'commit';
|
|
225
|
+
snapshots.push({
|
|
226
|
+
number: entry.number,
|
|
227
|
+
executionKind: kind,
|
|
228
|
+
data,
|
|
229
|
+
body: typeof taskDoc.body === 'string' ? taskDoc.body : '',
|
|
230
|
+
verification: readVerification(path.join(checkoutRoot, entry.verification.rel)),
|
|
231
|
+
review: kind === 'verification'
|
|
232
|
+
? null
|
|
233
|
+
: readReview(entry.review ? path.join(checkoutRoot, entry.review.rel) : null),
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
return snapshots;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* task trailer 매칭에 필요한 registry를 읽는다.
|
|
240
|
+
*
|
|
241
|
+
* @param {string} checkoutRoot - 읽을 checkout
|
|
242
|
+
* @returns {ReturnType<typeof readEpicRegistry>} registry 결과
|
|
243
|
+
*/
|
|
244
|
+
function readDigestRegistry(checkoutRoot) {
|
|
245
|
+
return readEpicRegistry(checkoutRoot);
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* PR 초안 생성 직전에 checkout config를 읽는다.
|
|
249
|
+
*
|
|
250
|
+
* @param {string} checkoutRoot - 읽을 checkout
|
|
251
|
+
* @returns {unknown} 현재 config 값
|
|
252
|
+
*/
|
|
253
|
+
function readDigestConfig(checkoutRoot) {
|
|
254
|
+
return readConfig(checkoutRoot);
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* digest에 필요한 trailer, diff, commit 로그와 branch를 같은 Git seam에서 수집한다.
|
|
258
|
+
*
|
|
259
|
+
* @param {object} opts - Git 수집 입력
|
|
260
|
+
* @param {string} opts.checkoutRoot - 대상 integration checkout
|
|
261
|
+
* @param {string} opts.base - 범위 시작 ref
|
|
262
|
+
* @param {string} opts.head - 범위 끝 ref
|
|
263
|
+
* @param {GitExec} opts.run - 모든 Git 명령의 seam
|
|
264
|
+
* @param {string[]} opts.stableIds - trailer를 매칭할 task id
|
|
265
|
+
* @param {Extract<ReturnType<typeof readEpicRegistry>, { status: 'ok' }>['registry'] | null} opts.registry
|
|
266
|
+
* 읽힌 registry 또는 null
|
|
267
|
+
* @param {string | null} opts.integrationBranch - 원장 branch 우선값
|
|
268
|
+
* @returns {object} Git stdout와 검증된 trailer·diff fingerprint
|
|
269
|
+
*/
|
|
270
|
+
function collectDigestGit({ checkoutRoot, base, head, run, stableIds, registry, integrationBranch, }) {
|
|
271
|
+
const trailerMap = resolveTaskCommits({ repoRoot: checkoutRoot, base, head, stableIds, exec: run, registry });
|
|
272
|
+
const nameOnly = run(['diff', '--name-only', `${base}..${head}`]);
|
|
273
|
+
const unified = run(['diff', '-U0', `${base}..${head}`]);
|
|
274
|
+
const subjectLog = run(['log', '--format=%h%x00%s', `${base}..${head}`]);
|
|
275
|
+
const numstat = run(['diff', '--numstat', '--no-renames', '-z', `${base}..${head}`]);
|
|
276
|
+
const diff = computeDiffSha({
|
|
277
|
+
repoRoot: checkoutRoot,
|
|
278
|
+
base,
|
|
279
|
+
exec: (args) => {
|
|
280
|
+
const result = run(args);
|
|
281
|
+
return { status: result.status, stdout: result.stdout, stderr: '' };
|
|
282
|
+
},
|
|
283
|
+
});
|
|
284
|
+
return {
|
|
285
|
+
trailerMap,
|
|
286
|
+
nameOnly,
|
|
287
|
+
unified,
|
|
288
|
+
subjectLog,
|
|
289
|
+
numstat,
|
|
290
|
+
diff,
|
|
291
|
+
branch: integrationBranch || resolveCheckoutBranch(checkoutRoot),
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* checkout에서 git을 읽고 외부 환경 변수로 대화형 입력을 막는다.
|
|
296
|
+
*
|
|
297
|
+
* @param {string} cwd - git 실행 checkout
|
|
298
|
+
* @param {string[]} args - git 뒤 argv
|
|
299
|
+
* @returns {{ status: number, stdout: string }} 호출 seam과 같은 형태의 결과
|
|
300
|
+
*/
|
|
301
|
+
function defaultExec(cwd, args) {
|
|
302
|
+
const result = spawnSync('git', args, {
|
|
303
|
+
cwd, encoding: 'utf8', env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
|
|
304
|
+
});
|
|
305
|
+
return {
|
|
306
|
+
status: typeof result.status === 'number' ? result.status : 1,
|
|
307
|
+
stdout: result.stdout || '',
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Digest 진입 단계에서 실패 우선순위가 있는 원장·task 목록·base·HEAD를 읽는다.
|
|
312
|
+
* task 문서 부재를 base 오류보다 먼저 판정해 기존 거절 순서를 보존한다.
|
|
313
|
+
*
|
|
314
|
+
* @param {object} opts - 수집에 필요한 경로와 git 실행 seam
|
|
315
|
+
* @param {string} opts.repoRoot - 호출 checkout 및 pointer 기준 경로
|
|
316
|
+
* @param {string} opts.blueprintDir - blueprint 상대 경로
|
|
317
|
+
* @param {GitExec} opts.run - 호출자가 설정한 git 실행 함수
|
|
318
|
+
* @returns {DigestCollection | DigestFail} 수집 snapshot 또는 첫 우선순위 실패
|
|
319
|
+
*/
|
|
320
|
+
function collectDigestStart({ repoRoot, blueprintDir, exec, }) {
|
|
321
|
+
const bp = toPosix(blueprintDir);
|
|
322
|
+
const blueprintAbs = path.join(repoRoot, bp);
|
|
323
|
+
if (!fs.existsSync(blueprintAbs))
|
|
324
|
+
return { ok: false, reason: 'blueprint-not-found' };
|
|
325
|
+
const ledgerRead = readCoordinatorLedger({ repoRoot, blueprint: bp });
|
|
326
|
+
if (!ledgerRead.ok && ledgerRead.reason === 'unreadable-ledger') {
|
|
327
|
+
return {
|
|
328
|
+
ok: false,
|
|
329
|
+
reason: 'coordinator-ledger',
|
|
330
|
+
code: 'UNREADABLE_LEDGER',
|
|
331
|
+
ledgerFile: ledgerRead.ledgerFile || null,
|
|
332
|
+
integrationPath: ledgerRead.integrationPath || null,
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
const checkoutRoot = ledgerRead.ok && ledgerRead.integrationPath
|
|
336
|
+
&& fs.existsSync(ledgerRead.integrationPath)
|
|
337
|
+
? ledgerRead.integrationPath
|
|
338
|
+
: repoRoot;
|
|
339
|
+
const run = typeof exec === 'function'
|
|
340
|
+
? exec
|
|
341
|
+
: (args) => defaultExec(checkoutRoot, args);
|
|
342
|
+
const listing = listTasksDocs({ repoRoot: checkoutRoot, blueprintDir: bp });
|
|
343
|
+
if (!listing.entries.some((entry) => fs.existsSync(path.join(checkoutRoot, entry.tasks.rel)))) {
|
|
344
|
+
return { ok: false, reason: 'task-documents-missing' };
|
|
345
|
+
}
|
|
346
|
+
const ledger = ledgerRead.ok ? ledgerRead.ledger : null;
|
|
347
|
+
const ledgerBase = ledger && typeof ledger.base === 'string'
|
|
348
|
+
? ledger.base
|
|
349
|
+
: null;
|
|
350
|
+
const base = resolveDigestBase({ repoRoot, blueprintDir: bp, ledgerBase });
|
|
351
|
+
if (!base)
|
|
352
|
+
return { ok: false, reason: 'no-base' };
|
|
353
|
+
const headResult = run(['rev-parse', 'HEAD']);
|
|
354
|
+
if (headResult.status !== 0 || !headResult.stdout.trim()) {
|
|
355
|
+
return { ok: false, reason: 'no-base' };
|
|
356
|
+
}
|
|
357
|
+
const head = headResult.stdout.trim();
|
|
358
|
+
const verified = run(['rev-parse', '--verify', `${base}^{commit}`]);
|
|
359
|
+
if (verified.status !== 0)
|
|
360
|
+
return { ok: false, reason: 'no-base' };
|
|
361
|
+
return { ledgerRead, checkoutRoot, listing, ledger, base, head, run };
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* drive 원장 base 또는 같은 blueprint pointer의 base를 선택한다.
|
|
365
|
+
* pointer 오류는 기존 계약대로 base 부재로 접고 pointer 상태를 수정하지 않는다.
|
|
366
|
+
*
|
|
367
|
+
* @param {object} opts - 저장소와 원장 base
|
|
368
|
+
* @param {string} opts.repoRoot - pointer를 읽을 저장소
|
|
369
|
+
* @param {string} opts.blueprintDir - 요청된 blueprint 상대 경로
|
|
370
|
+
* @param {string | null} opts.ledgerBase - 원장 base. 없으면 pointer를 확인
|
|
371
|
+
* @returns {string | null} 유효한 base 또는 null
|
|
372
|
+
*/
|
|
373
|
+
function resolveDigestBase({ repoRoot, blueprintDir, ledgerBase, }) {
|
|
374
|
+
if (typeof ledgerBase === 'string' && ledgerBase.trim())
|
|
375
|
+
return ledgerBase.trim();
|
|
376
|
+
try {
|
|
377
|
+
const pointer = readCurrent({ repoRoot });
|
|
378
|
+
if (pointer && typeof pointer.blueprint === 'string'
|
|
379
|
+
&& toPosix(pointer.blueprint) === toPosix(blueprintDir)
|
|
380
|
+
&& typeof pointer.base === 'string' && pointer.base.trim()) {
|
|
381
|
+
return pointer.base.trim();
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
catch (_error) {
|
|
385
|
+
// CURRENT_AMBIGUOUS 등은 base 부재와 같다 — digest는 pointer를 고치지 않는다.
|
|
386
|
+
}
|
|
387
|
+
return null;
|
|
388
|
+
}
|