ultra-builder-pro-cli 5.0.0 → 6.0.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/CHANGELOG.md +113 -3
- package/README.md +67 -478
- package/README.zh-CN.md +49 -422
- package/adapters/_shared/frontmatter.cjs +1 -18
- package/adapters/_shared/plugin-core.cjs +17 -83
- package/adapters/_shared/runtime-assets.cjs +6 -120
- package/adapters/claude.js +8 -24
- package/adapters/codex.js +16 -38
- package/bin/install.js +35 -57
- package/docs/PLUGIN-ISOLATION-CONTRACT.md +13 -101
- package/docs/examples/drive/claude-reviewer.sh +53 -0
- package/docs/examples/drive/claude-writer.sh +43 -0
- package/engine/FORMATS.md +1051 -0
- package/engine/checks.cjs +139 -0
- package/engine/cli.cjs +148 -0
- package/engine/completion.cjs +48 -0
- package/engine/context.cjs +94 -0
- package/engine/drive.cjs +220 -0
- package/engine/expect-worker.cjs +5 -0
- package/engine/expect.cjs +33 -0
- package/engine/formats.cjs +189 -0
- package/engine/git-state.cjs +112 -0
- package/engine/ledger.cjs +205 -0
- package/engine/loading.cjs +28 -0
- package/engine/merge.cjs +273 -0
- package/engine/milestone.cjs +89 -0
- package/engine/plan.cjs +232 -0
- package/engine/project.cjs +5 -0
- package/engine/signals.cjs +3 -0
- package/engine/states.cjs +175 -0
- package/hooks/session-start.cjs +54 -0
- package/package.json +17 -52
- package/skills/ultra/SKILL.md +395 -0
- package/.ultra-template/changes/abandoned/.gitkeep +0 -0
- package/.ultra-template/changes/active/.gitkeep +0 -1
- package/.ultra-template/changes/archive/.gitkeep +0 -1
- package/.ultra-template/contexts/TEMPLATE.md +0 -135
- package/.ultra-template/decisions/.gitkeep +0 -1
- package/.ultra-template/evidence/.gitkeep +0 -1
- package/.ultra-template/north-star.md +0 -52
- package/.ultra-template/project-brief.md +0 -28
- package/.ultra-template/research/.gitkeep +0 -1
- package/.ultra-template/specs/architecture.md +0 -81
- package/.ultra-template/specs/discovery.md +0 -74
- package/.ultra-template/specs/product.md +0 -80
- package/.ultra-template/specs/research-distillate.md +0 -57
- package/.ultra-template/tasks.json +0 -4
- package/.ultra-template/test-report.json +0 -44
- package/adapters/_shared/host-profile.cjs +0 -5
- package/adapters/grok.js +0 -242
- package/adapters/kimi.js +0 -232
- package/adapters/opencode.js +0 -408
- package/adapters/zcode.js +0 -375
- package/bin/delegate-worker.cjs +0 -5
- package/bin/delegate.cjs +0 -5
- package/docs/ARCHITECTURE.md +0 -274
- package/docs/ARTIFACT-AUTHORITY.md +0 -305
- package/docs/DECISIONS.md +0 -229
- package/docs/PHILOSOPHY.md +0 -261
- package/docs/RUNTIME-COMPAT-MATRIX.md +0 -315
- package/docs/SKILL-AUTHORING.md +0 -113
- package/docs/WORKFLOW-LIFECYCLE.md +0 -350
- package/docs/evals/adversarial-review-2026-08-14.md +0 -84
- package/docs/evals/ultra-5-0-journey-results.md +0 -62
- package/docs/evals/ultra-5-0-live-acp-conformance.md +0 -93
- package/docs/evals/ultra-5-0-route-journeys.md +0 -145
- package/docs/evals/zcode-automation-2026-08-14.md +0 -103
- package/hooks/README.md +0 -52
- package/hooks/_common.py +0 -1779
- package/hooks/adapters/codex.py +0 -295
- package/hooks/adapters/grok.py +0 -134
- package/hooks/adapters/kimi.py +0 -156
- package/hooks/adapters/zcode.py +0 -227
- package/hooks/block_dangerous_commands.py +0 -201
- package/hooks/compact_context.py +0 -224
- package/hooks/mid_workflow_recall.py +0 -47
- package/hooks/post_edit_guard.py +0 -215
- package/hooks/session_context.py +0 -741
- package/skills/ultra-change/SKILL.md +0 -165
- package/skills/ultra-change/references/change-contract.md +0 -199
- package/skills/ultra-change/references/execution-grant.md +0 -109
- package/skills/ultra-change/references/primary-transfer.md +0 -273
- package/skills/ultra-change/scripts/validate_primary_transfer.cjs +0 -1928
- package/skills/ultra-delegate/SKILL.md +0 -195
- package/skills/ultra-delegate/patches/zcode-acp-0.1.0-zcode-0.16.5.patch +0 -184
- package/skills/ultra-delegate/references/delegation-contract.md +0 -138
- package/skills/ultra-delegate/scripts/acp-conformance.cjs +0 -287
- package/skills/ultra-delegate/scripts/acp-driver.cjs +0 -762
- package/skills/ultra-delegate/scripts/agent-execution-driver.cjs +0 -320
- package/skills/ultra-delegate/scripts/cli-driver.cjs +0 -61
- package/skills/ultra-delegate/scripts/delegate-profiles/kimi-read-only.md +0 -15
- package/skills/ultra-delegate/scripts/delegate-profiles/kimi-write.md +0 -17
- package/skills/ultra-delegate/scripts/delegate-worker.cjs +0 -328
- package/skills/ultra-delegate/scripts/delegate.cjs +0 -690
- package/skills/ultra-delegate/scripts/delegate_wait.py +0 -86
- package/skills/ultra-delegate/scripts/host-profile.cjs +0 -267
- package/skills/ultra-deliver/SKILL.md +0 -194
- package/skills/ultra-deliver/references/baseline-reconciliation.md +0 -41
- package/skills/ultra-dev/SKILL.md +0 -265
- package/skills/ultra-dev/references/debugging.md +0 -22
- package/skills/ultra-domain-modeling/SKILL.md +0 -79
- package/skills/ultra-grilling/SKILL.md +0 -121
- package/skills/ultra-grilling/references/reframing.md +0 -76
- package/skills/ultra-init/SKILL.md +0 -143
- package/skills/ultra-init/references/brownfield-adoption.md +0 -32
- package/skills/ultra-init/scripts/init_project.cjs +0 -1031
- package/skills/ultra-plan/SKILL.md +0 -160
- package/skills/ultra-plan/references/task-evidence-v2.md +0 -300
- package/skills/ultra-plan/scripts/validate_task_evidence.cjs +0 -859
- package/skills/ultra-research/SKILL.md +0 -167
- package/skills/ultra-research/references/00-problem-validation.md +0 -36
- package/skills/ultra-research/references/01-opportunity-discovery.md +0 -33
- package/skills/ultra-research/references/02-market-assessment.md +0 -31
- package/skills/ultra-research/references/03-alternatives.md +0 -31
- package/skills/ultra-research/references/04-product-strategy.md +0 -41
- package/skills/ultra-research/references/05-assumptions-validation.md +0 -31
- package/skills/ultra-research/references/10-user-personas.md +0 -30
- package/skills/ultra-research/references/11-user-scenarios.md +0 -28
- package/skills/ultra-research/references/20-user-stories.md +0 -31
- package/skills/ultra-research/references/21-features-scope.md +0 -38
- package/skills/ultra-research/references/22-success-metrics.md +0 -33
- package/skills/ultra-research/references/30-architecture-context.md +0 -30
- package/skills/ultra-research/references/31-solution-strategy.md +0 -28
- package/skills/ultra-research/references/32-building-blocks.md +0 -28
- package/skills/ultra-research/references/40-deployment.md +0 -29
- package/skills/ultra-research/references/41-quality-risks.md +0 -28
- package/skills/ultra-research/references/99-synthesis.md +0 -59
- package/skills/ultra-research/references/north-star-v2.md +0 -168
- package/skills/ultra-research/references/wayfinding.md +0 -50
- package/skills/ultra-research/scripts/validate_north_star.cjs +0 -1174
- package/skills/ultra-review/SKILL.md +0 -275
- package/skills/ultra-review/references/adversarial-evaluation.md +0 -31
- package/skills/ultra-review/references/code.md +0 -17
- package/skills/ultra-review/references/comments.md +0 -12
- package/skills/ultra-review/references/design.md +0 -12
- package/skills/ultra-review/references/errors.md +0 -13
- package/skills/ultra-review/references/review-modes.md +0 -29
- package/skills/ultra-review/references/spec.md +0 -18
- package/skills/ultra-review/references/tests.md +0 -12
- package/skills/ultra-review/references/unified-schema.md +0 -284
- package/skills/ultra-review/references/worker-packet.md +0 -183
- package/skills/ultra-review/scripts/review_wait.py +0 -2943
- package/skills/ultra-status/SKILL.md +0 -209
- package/skills/ultra-status/references/route-contract.md +0 -100
- package/skills/ultra-tdd/SKILL.md +0 -91
- package/skills/ultra-tdd/references/templates/README.md +0 -20
- package/skills/ultra-tdd/references/templates/feature-flag-default-audit.sh +0 -79
- package/skills/ultra-tdd/references/templates/persistence-real.ts +0 -78
- package/skills/ultra-tdd/references/templates/testcontainer-postgres.py +0 -78
- package/skills/ultra-tdd/references/templates/testcontainer-postgres.ts +0 -73
- package/skills/ultra-tdd/references/templates/vertical-slice.ts +0 -66
- package/skills/ultra-tdd/references/test-execution.md +0 -23
- package/skills/ultra-test/SKILL.md +0 -244
- package/skills/ultra-test/references/export-syntax.md +0 -18
- package/skills/ultra-test/scripts/validate_review_transport.cjs +0 -549
- package/skills/ultra-test/scripts/worktree_digest.cjs +0 -576
- package/skills/ultra-think/SKILL.md +0 -56
- package/skills/ultra-think/references/autonomy-boundary.md +0 -48
- package/skills/ultra-think/references/interaction-boundary.md +0 -42
|
@@ -1,1031 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
const crypto = require('node:crypto');
|
|
5
|
-
const fs = require('node:fs');
|
|
6
|
-
const path = require('node:path');
|
|
7
|
-
|
|
8
|
-
const SNAPSHOT_READ_CHUNK_BYTES = 64 * 1024;
|
|
9
|
-
|
|
10
|
-
function usage() {
|
|
11
|
-
return 'usage: init_project.cjs --project <repository-root>';
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function parseArgs(argv) {
|
|
15
|
-
if (argv.length !== 2 || argv[0] !== '--project' || !argv[1]) {
|
|
16
|
-
throw new Error(usage());
|
|
17
|
-
}
|
|
18
|
-
return path.resolve(argv[1]);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function listFiles(root) {
|
|
22
|
-
const files = [];
|
|
23
|
-
(function walk(directory) {
|
|
24
|
-
for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
|
|
25
|
-
const file = path.join(directory, entry.name);
|
|
26
|
-
if (entry.isDirectory()) walk(file);
|
|
27
|
-
else if (entry.isFile()) files.push(path.relative(root, file));
|
|
28
|
-
}
|
|
29
|
-
}(root));
|
|
30
|
-
return files.sort();
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function templateRoot() {
|
|
34
|
-
const installed = path.resolve(__dirname, '..', 'assets', 'project-template');
|
|
35
|
-
if (fs.existsSync(installed)) return installed;
|
|
36
|
-
const checkout = path.resolve(__dirname, '..', '..', '..', '.ultra-template');
|
|
37
|
-
if (fs.existsSync(checkout)) return checkout;
|
|
38
|
-
throw new Error('ultra-init project template is missing');
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function ensurePlainDirectory(directory, label) {
|
|
42
|
-
const stat = fs.lstatSync(directory);
|
|
43
|
-
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
44
|
-
throw new Error(`${label} must be a real directory: ${directory}`);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function northStarValidator() {
|
|
49
|
-
const validator = path.resolve(
|
|
50
|
-
__dirname,
|
|
51
|
-
'..',
|
|
52
|
-
'..',
|
|
53
|
-
'ultra-research',
|
|
54
|
-
'scripts',
|
|
55
|
-
'validate_north_star.cjs',
|
|
56
|
-
);
|
|
57
|
-
return fs.existsSync(validator) ? require(validator) : null;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function identity(stat) {
|
|
61
|
-
return [
|
|
62
|
-
stat.dev,
|
|
63
|
-
stat.ino,
|
|
64
|
-
stat.mode,
|
|
65
|
-
stat.size,
|
|
66
|
-
stat.mtimeNs,
|
|
67
|
-
stat.ctimeNs,
|
|
68
|
-
];
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function sameIdentity(left, right) {
|
|
72
|
-
return left.length === right.length && left.every((value, index) => value === right[index]);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function objectIdentity(snapshotIdentity) {
|
|
76
|
-
return snapshotIdentity.slice(0, 3);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function directoryIdentity(stat) {
|
|
80
|
-
return [stat.dev, stat.ino, stat.mode];
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function preservedNorthStarConflict(phase) {
|
|
84
|
-
const error = new Error(
|
|
85
|
-
'Preserved North Star changed during initialization; retry after the file is stable.',
|
|
86
|
-
);
|
|
87
|
-
error.code = 'preserved_north_star_changed';
|
|
88
|
-
error.retryable = true;
|
|
89
|
-
error.phase = phase;
|
|
90
|
-
error.path = 'north-star.md';
|
|
91
|
-
return error;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function northStarTemplateInvalid(report) {
|
|
95
|
-
const error = new Error(
|
|
96
|
-
'new North Star template must pass the full North Star validator as an exact unresearched placeholder',
|
|
97
|
-
);
|
|
98
|
-
error.code = 'north_star_template_invalid';
|
|
99
|
-
error.retryable = true;
|
|
100
|
-
error.phase = 'template_validation';
|
|
101
|
-
error.path = 'north-star.md';
|
|
102
|
-
error.diagnostics = Array.isArray(report?.diagnostics)
|
|
103
|
-
? report.diagnostics.map((diagnostic) => ({ ...diagnostic }))
|
|
104
|
-
: [];
|
|
105
|
-
return error;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function initializationSnapshotConflict(relative, phase, preservedNorthStar = false) {
|
|
109
|
-
if (preservedNorthStar) return preservedNorthStarConflict(phase);
|
|
110
|
-
const error = new Error(
|
|
111
|
-
`Initialization path changed during ${phase}: ${relative}; preserve the current bytes and retry after workspace writes settle.`,
|
|
112
|
-
);
|
|
113
|
-
error.code = 'initialization_snapshot_changed';
|
|
114
|
-
error.retryable = true;
|
|
115
|
-
error.phase = phase;
|
|
116
|
-
error.path = relative;
|
|
117
|
-
return error;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function initializationSnapshotIoError(relative, phase, operation, cause) {
|
|
121
|
-
const allowedOperations = new Set(['lstat', 'open', 'fstat', 'read']);
|
|
122
|
-
const safeOperation = allowedOperations.has(operation) ? operation : 'unknown';
|
|
123
|
-
const safeErrno = typeof cause?.code === 'string'
|
|
124
|
-
&& /^[A-Z][A-Z0-9_]*$/u.test(cause.code)
|
|
125
|
-
? cause.code
|
|
126
|
-
: 'UNKNOWN';
|
|
127
|
-
let manualAction = 'Repair the reported filesystem I/O condition, then retry initialization.';
|
|
128
|
-
if (safeErrno === 'EACCES' || safeErrno === 'EPERM') {
|
|
129
|
-
manualAction = 'Check permissions and ownership for the path, then retry initialization.';
|
|
130
|
-
} else if (safeErrno === 'EMFILE' || safeErrno === 'ENFILE') {
|
|
131
|
-
manualAction = 'Release unused file descriptors or raise the process descriptor limit, then retry initialization.';
|
|
132
|
-
} else if (safeErrno === 'EIO') {
|
|
133
|
-
manualAction = 'Check filesystem availability and storage health for the path, then retry initialization.';
|
|
134
|
-
}
|
|
135
|
-
const error = new Error(
|
|
136
|
-
`Initialization snapshot ${safeOperation} failed during ${phase}: ${relative} (${safeErrno}).`,
|
|
137
|
-
);
|
|
138
|
-
error.code = 'initialization_snapshot_io_error';
|
|
139
|
-
error.retryable = true;
|
|
140
|
-
error.phase = phase;
|
|
141
|
-
error.path = relative;
|
|
142
|
-
error.operation = safeOperation;
|
|
143
|
-
error.errno = safeErrno;
|
|
144
|
-
error.recovery = { manual_action: manualAction };
|
|
145
|
-
return error;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function snapshotIo(relative, phase, operation, read) {
|
|
149
|
-
try {
|
|
150
|
-
return read();
|
|
151
|
-
} catch (error) {
|
|
152
|
-
throw initializationSnapshotIoError(relative, phase, operation, error);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
function stageCleanupConflict(stage, snapshot, {
|
|
157
|
-
currentPathPreserved,
|
|
158
|
-
destructiveCleanupAttempted = false,
|
|
159
|
-
} = {}) {
|
|
160
|
-
const error = new Error(
|
|
161
|
-
`Staging directory identity changed before cleanup: ${stage}. `
|
|
162
|
-
+ 'No replacement path was removed; locate the owned directory by device and inode before manual removal.',
|
|
163
|
-
);
|
|
164
|
-
error.code = 'initialization_cleanup_conflict';
|
|
165
|
-
error.retryable = true;
|
|
166
|
-
error.phase = 'stage_cleanup';
|
|
167
|
-
error.path = path.basename(stage);
|
|
168
|
-
error.recovery = {
|
|
169
|
-
current_path: stage,
|
|
170
|
-
current_path_preserved: Boolean(currentPathPreserved),
|
|
171
|
-
destructive_cleanup_attempted: Boolean(destructiveCleanupAttempted),
|
|
172
|
-
owned_directory: {
|
|
173
|
-
device: String(snapshot.identity[0]),
|
|
174
|
-
inode: String(snapshot.identity[1]),
|
|
175
|
-
},
|
|
176
|
-
manual_action: 'Locate the owned staging directory by device and inode; remove it only after verifying that identity. Do not remove a replacement at current_path.',
|
|
177
|
-
};
|
|
178
|
-
return error;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
function stableFileSnapshot(file, relative, phase, {
|
|
182
|
-
preservedNorthStar = false,
|
|
183
|
-
retainBytes = false,
|
|
184
|
-
} = {}) {
|
|
185
|
-
let descriptor;
|
|
186
|
-
try {
|
|
187
|
-
const beforePath = snapshotIo(
|
|
188
|
-
relative,
|
|
189
|
-
phase,
|
|
190
|
-
'lstat',
|
|
191
|
-
() => fs.lstatSync(file, { bigint: true }),
|
|
192
|
-
);
|
|
193
|
-
if (!beforePath.isFile() || beforePath.isSymbolicLink()) {
|
|
194
|
-
throw initializationSnapshotConflict(relative, phase, preservedNorthStar);
|
|
195
|
-
}
|
|
196
|
-
const noFollow = fs.constants.O_NOFOLLOW || 0;
|
|
197
|
-
descriptor = snapshotIo(
|
|
198
|
-
relative,
|
|
199
|
-
phase,
|
|
200
|
-
'open',
|
|
201
|
-
() => fs.openSync(
|
|
202
|
-
file,
|
|
203
|
-
fs.constants.O_RDONLY | fs.constants.O_NONBLOCK | noFollow,
|
|
204
|
-
),
|
|
205
|
-
);
|
|
206
|
-
const before = snapshotIo(
|
|
207
|
-
relative,
|
|
208
|
-
phase,
|
|
209
|
-
'fstat',
|
|
210
|
-
() => fs.fstatSync(descriptor, { bigint: true }),
|
|
211
|
-
);
|
|
212
|
-
if (!before.isFile() || !sameIdentity(identity(beforePath), identity(before))) {
|
|
213
|
-
throw initializationSnapshotConflict(relative, phase, preservedNorthStar);
|
|
214
|
-
}
|
|
215
|
-
const hash = crypto.createHash('sha256');
|
|
216
|
-
const retainedChunks = retainBytes ? [] : null;
|
|
217
|
-
const readBuffer = Buffer.allocUnsafe(SNAPSHOT_READ_CHUNK_BYTES);
|
|
218
|
-
let size = 0;
|
|
219
|
-
while (true) {
|
|
220
|
-
const bytesRead = snapshotIo(
|
|
221
|
-
relative,
|
|
222
|
-
phase,
|
|
223
|
-
'read',
|
|
224
|
-
() => fs.readSync(
|
|
225
|
-
descriptor,
|
|
226
|
-
readBuffer,
|
|
227
|
-
0,
|
|
228
|
-
readBuffer.length,
|
|
229
|
-
null,
|
|
230
|
-
),
|
|
231
|
-
);
|
|
232
|
-
if (bytesRead === 0) break;
|
|
233
|
-
const chunk = readBuffer.subarray(0, bytesRead);
|
|
234
|
-
hash.update(chunk);
|
|
235
|
-
if (retainedChunks) retainedChunks.push(Buffer.from(chunk));
|
|
236
|
-
size += bytesRead;
|
|
237
|
-
}
|
|
238
|
-
const after = snapshotIo(
|
|
239
|
-
relative,
|
|
240
|
-
phase,
|
|
241
|
-
'fstat',
|
|
242
|
-
() => fs.fstatSync(descriptor, { bigint: true }),
|
|
243
|
-
);
|
|
244
|
-
const afterPath = snapshotIo(
|
|
245
|
-
relative,
|
|
246
|
-
phase,
|
|
247
|
-
'lstat',
|
|
248
|
-
() => fs.lstatSync(file, { bigint: true }),
|
|
249
|
-
);
|
|
250
|
-
if (!sameIdentity(identity(before), identity(after))
|
|
251
|
-
|| !sameIdentity(identity(after), identity(afterPath))
|
|
252
|
-
|| BigInt(size) !== after.size) {
|
|
253
|
-
throw initializationSnapshotConflict(relative, phase, preservedNorthStar);
|
|
254
|
-
}
|
|
255
|
-
const snapshot = {
|
|
256
|
-
size,
|
|
257
|
-
digest: hash.digest('hex'),
|
|
258
|
-
identity: identity(after),
|
|
259
|
-
};
|
|
260
|
-
if (retainedChunks) snapshot.bytes = Buffer.concat(retainedChunks, size);
|
|
261
|
-
return snapshot;
|
|
262
|
-
} finally {
|
|
263
|
-
if (descriptor !== undefined) fs.closeSync(descriptor);
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
function assertFileSnapshotUnchanged(
|
|
268
|
-
file,
|
|
269
|
-
relative,
|
|
270
|
-
expected,
|
|
271
|
-
phase,
|
|
272
|
-
preservedNorthStar = false,
|
|
273
|
-
) {
|
|
274
|
-
const observed = stableFileSnapshot(file, relative, phase, { preservedNorthStar });
|
|
275
|
-
if (!sameIdentity(expected.identity, observed.identity)
|
|
276
|
-
|| expected.digest !== observed.digest) {
|
|
277
|
-
throw initializationSnapshotConflict(relative, phase, preservedNorthStar);
|
|
278
|
-
}
|
|
279
|
-
return observed;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
function stableRepositoryFileSnapshot(
|
|
283
|
-
root,
|
|
284
|
-
ultra,
|
|
285
|
-
relative,
|
|
286
|
-
phase,
|
|
287
|
-
knownDirectories,
|
|
288
|
-
options = {},
|
|
289
|
-
) {
|
|
290
|
-
const file = path.join(ultra, relative);
|
|
291
|
-
const parent = path.dirname(file);
|
|
292
|
-
const before = captureDirectoryChain(
|
|
293
|
-
root,
|
|
294
|
-
parent,
|
|
295
|
-
relative,
|
|
296
|
-
phase,
|
|
297
|
-
knownDirectories,
|
|
298
|
-
);
|
|
299
|
-
const snapshot = stableFileSnapshot(file, relative, phase, options);
|
|
300
|
-
const after = captureDirectoryChain(
|
|
301
|
-
root,
|
|
302
|
-
parent,
|
|
303
|
-
relative,
|
|
304
|
-
phase,
|
|
305
|
-
knownDirectories,
|
|
306
|
-
);
|
|
307
|
-
assertSameDirectoryChain(before, after, relative, phase);
|
|
308
|
-
return snapshot;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
function assertPreservedFilesUnchanged(
|
|
312
|
-
root,
|
|
313
|
-
ultra,
|
|
314
|
-
snapshots,
|
|
315
|
-
phase,
|
|
316
|
-
knownDirectories,
|
|
317
|
-
) {
|
|
318
|
-
for (const [relative, expected] of snapshots) {
|
|
319
|
-
const observed = stableRepositoryFileSnapshot(
|
|
320
|
-
root,
|
|
321
|
-
ultra,
|
|
322
|
-
relative,
|
|
323
|
-
phase,
|
|
324
|
-
knownDirectories,
|
|
325
|
-
{ preservedNorthStar: relative === 'north-star.md' },
|
|
326
|
-
);
|
|
327
|
-
if (!sameIdentity(expected.identity, observed.identity)
|
|
328
|
-
|| expected.digest !== observed.digest) {
|
|
329
|
-
throw initializationSnapshotConflict(
|
|
330
|
-
relative,
|
|
331
|
-
phase,
|
|
332
|
-
relative === 'north-star.md',
|
|
333
|
-
);
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
function captureRoot(project) {
|
|
339
|
-
const before = fs.lstatSync(project, { bigint: true });
|
|
340
|
-
if (!before.isDirectory() || before.isSymbolicLink()) {
|
|
341
|
-
throw initializationSnapshotConflict('.', 'root_snapshot');
|
|
342
|
-
}
|
|
343
|
-
const resolved = fs.realpathSync.native(project);
|
|
344
|
-
const after = fs.lstatSync(project, { bigint: true });
|
|
345
|
-
if (!after.isDirectory()
|
|
346
|
-
|| after.isSymbolicLink()
|
|
347
|
-
|| !sameIdentity(directoryIdentity(before), directoryIdentity(after))) {
|
|
348
|
-
throw initializationSnapshotConflict('.', 'root_snapshot');
|
|
349
|
-
}
|
|
350
|
-
return {
|
|
351
|
-
path: project,
|
|
352
|
-
resolved,
|
|
353
|
-
identity: directoryIdentity(after),
|
|
354
|
-
};
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
function captureDirectoryChain(root, directory, relative, phase, knownDirectories) {
|
|
358
|
-
const target = path.resolve(directory);
|
|
359
|
-
const withinRoot = path.relative(root.path, target);
|
|
360
|
-
if (withinRoot === '..'
|
|
361
|
-
|| withinRoot.startsWith(`..${path.sep}`)
|
|
362
|
-
|| path.isAbsolute(withinRoot)) {
|
|
363
|
-
throw initializationSnapshotConflict(relative, phase);
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
const parts = withinRoot === '' ? [] : withinRoot.split(path.sep);
|
|
367
|
-
const chain = [];
|
|
368
|
-
let current = root.path;
|
|
369
|
-
for (let index = 0; index <= parts.length; index += 1) {
|
|
370
|
-
if (index > 0) current = path.join(current, parts[index - 1]);
|
|
371
|
-
let entry;
|
|
372
|
-
let resolved;
|
|
373
|
-
try {
|
|
374
|
-
entry = fs.lstatSync(current, { bigint: true });
|
|
375
|
-
if (!entry.isDirectory() || entry.isSymbolicLink()) {
|
|
376
|
-
throw initializationSnapshotConflict(relative, phase);
|
|
377
|
-
}
|
|
378
|
-
resolved = fs.realpathSync.native(current);
|
|
379
|
-
} catch (error) {
|
|
380
|
-
if (error.code === 'initialization_snapshot_changed') throw error;
|
|
381
|
-
throw initializationSnapshotConflict(relative, phase);
|
|
382
|
-
}
|
|
383
|
-
const expectedResolved = path.join(root.resolved, ...parts.slice(0, index));
|
|
384
|
-
const snapshot = {
|
|
385
|
-
path: current,
|
|
386
|
-
resolved,
|
|
387
|
-
identity: directoryIdentity(entry),
|
|
388
|
-
};
|
|
389
|
-
if (resolved !== expectedResolved) {
|
|
390
|
-
throw initializationSnapshotConflict(relative, phase);
|
|
391
|
-
}
|
|
392
|
-
const known = knownDirectories.get(current);
|
|
393
|
-
if (known
|
|
394
|
-
&& (known.resolved !== snapshot.resolved
|
|
395
|
-
|| !sameIdentity(known.identity, snapshot.identity))) {
|
|
396
|
-
throw initializationSnapshotConflict(relative, phase);
|
|
397
|
-
}
|
|
398
|
-
if (!known) knownDirectories.set(current, snapshot);
|
|
399
|
-
chain.push(snapshot);
|
|
400
|
-
}
|
|
401
|
-
if (root.resolved !== chain[0].resolved
|
|
402
|
-
|| !sameIdentity(root.identity, chain[0].identity)) {
|
|
403
|
-
throw initializationSnapshotConflict(relative, phase);
|
|
404
|
-
}
|
|
405
|
-
return chain;
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
function assertSameDirectoryChain(before, after, relative, phase) {
|
|
409
|
-
if (before.length !== after.length) {
|
|
410
|
-
throw initializationSnapshotConflict(relative, phase);
|
|
411
|
-
}
|
|
412
|
-
for (let index = 0; index < before.length; index += 1) {
|
|
413
|
-
if (before[index].path !== after[index].path
|
|
414
|
-
|| before[index].resolved !== after[index].resolved
|
|
415
|
-
|| !sameIdentity(before[index].identity, after[index].identity)) {
|
|
416
|
-
throw initializationSnapshotConflict(relative, phase);
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
function classifyPreservedNorthStar(file, snapshot) {
|
|
422
|
-
const validator = northStarValidator();
|
|
423
|
-
if (validator?.validateBytes) {
|
|
424
|
-
const report = validator.validateBytes(file, snapshot.bytes);
|
|
425
|
-
if (report.valid && report.kind === 'legacy') return { disposition: 'preserved_legacy' };
|
|
426
|
-
if (report.valid && report.kind === 'north-star-v2' && report.status === 'unresearched') {
|
|
427
|
-
return { disposition: 'preserved_unresearched' };
|
|
428
|
-
}
|
|
429
|
-
if (report.valid && report.kind === 'north-star-v2' && report.status === 'accepted') {
|
|
430
|
-
return { disposition: 'preserved_accepted' };
|
|
431
|
-
}
|
|
432
|
-
return {
|
|
433
|
-
disposition: 'preserved_unknown',
|
|
434
|
-
diagnostics: Array.isArray(report.diagnostics) ? report.diagnostics : [],
|
|
435
|
-
};
|
|
436
|
-
}
|
|
437
|
-
return {
|
|
438
|
-
disposition: 'preserved_unknown',
|
|
439
|
-
diagnostics: [{
|
|
440
|
-
code: 'north_star_validator_missing',
|
|
441
|
-
severity: 'error',
|
|
442
|
-
message: 'Full North Star validator is missing from the installed plugin',
|
|
443
|
-
location: file,
|
|
444
|
-
}],
|
|
445
|
-
};
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
function validateNewNorthStar(file) {
|
|
449
|
-
const validator = northStarValidator();
|
|
450
|
-
if (!validator) throw new Error('full North Star validator is missing from the installed plugin');
|
|
451
|
-
const report = validator.validate(file);
|
|
452
|
-
const idsEmpty = ['FP', 'NS', 'HC'].every(
|
|
453
|
-
(kind) => Array.isArray(report.ids?.[kind]) && report.ids[kind].length === 0,
|
|
454
|
-
);
|
|
455
|
-
if (!(report.valid
|
|
456
|
-
&& report.kind === 'north-star-v2'
|
|
457
|
-
&& report.status === 'unresearched'
|
|
458
|
-
&& idsEmpty)) {
|
|
459
|
-
throw northStarTemplateInvalid(report);
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
function stageFiles(source, relativeFiles, stage) {
|
|
464
|
-
const snapshots = new Map();
|
|
465
|
-
for (const relative of relativeFiles) {
|
|
466
|
-
const target = path.join(stage, relative);
|
|
467
|
-
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
468
|
-
fs.copyFileSync(path.join(source, relative), target, fs.constants.COPYFILE_EXCL);
|
|
469
|
-
snapshots.set(relative, stableFileSnapshot(target, relative, 'stage_snapshot'));
|
|
470
|
-
}
|
|
471
|
-
return snapshots;
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
function ensurePublishDirectory(
|
|
475
|
-
directory,
|
|
476
|
-
ultra,
|
|
477
|
-
createdDirectories,
|
|
478
|
-
root,
|
|
479
|
-
knownDirectories,
|
|
480
|
-
relative,
|
|
481
|
-
) {
|
|
482
|
-
if (directory === ultra) {
|
|
483
|
-
captureDirectoryChain(root, directory, relative, 'publish_parent', knownDirectories);
|
|
484
|
-
return;
|
|
485
|
-
}
|
|
486
|
-
const parent = path.dirname(directory);
|
|
487
|
-
ensurePublishDirectory(
|
|
488
|
-
parent,
|
|
489
|
-
ultra,
|
|
490
|
-
createdDirectories,
|
|
491
|
-
root,
|
|
492
|
-
knownDirectories,
|
|
493
|
-
relative,
|
|
494
|
-
);
|
|
495
|
-
if (fs.existsSync(directory)) {
|
|
496
|
-
captureDirectoryChain(root, directory, relative, 'publish_parent', knownDirectories);
|
|
497
|
-
return;
|
|
498
|
-
}
|
|
499
|
-
const before = captureDirectoryChain(
|
|
500
|
-
root,
|
|
501
|
-
parent,
|
|
502
|
-
relative,
|
|
503
|
-
'publish_parent_before',
|
|
504
|
-
knownDirectories,
|
|
505
|
-
);
|
|
506
|
-
try {
|
|
507
|
-
fs.mkdirSync(directory);
|
|
508
|
-
} catch {
|
|
509
|
-
throw initializationSnapshotConflict(relative, 'publish_parent');
|
|
510
|
-
}
|
|
511
|
-
const afterParent = captureDirectoryChain(
|
|
512
|
-
root,
|
|
513
|
-
parent,
|
|
514
|
-
relative,
|
|
515
|
-
'publish_parent_after',
|
|
516
|
-
knownDirectories,
|
|
517
|
-
);
|
|
518
|
-
assertSameDirectoryChain(before, afterParent, relative, 'publish_parent_after');
|
|
519
|
-
const chain = captureDirectoryChain(
|
|
520
|
-
root,
|
|
521
|
-
directory,
|
|
522
|
-
relative,
|
|
523
|
-
'publish_parent_after',
|
|
524
|
-
knownDirectories,
|
|
525
|
-
);
|
|
526
|
-
const snapshot = chain[chain.length - 1];
|
|
527
|
-
createdDirectories.push({
|
|
528
|
-
path: directory,
|
|
529
|
-
relative: path.relative(ultra, directory) || '.',
|
|
530
|
-
snapshot,
|
|
531
|
-
});
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
function rollbackPublishedFiles(
|
|
535
|
-
published,
|
|
536
|
-
createdDirectories,
|
|
537
|
-
root,
|
|
538
|
-
knownDirectories,
|
|
539
|
-
) {
|
|
540
|
-
let conflict = null;
|
|
541
|
-
for (const item of [...published].reverse()) {
|
|
542
|
-
try {
|
|
543
|
-
const current = stableFileSnapshot(item.target, item.relative, 'rollback');
|
|
544
|
-
if (!item.snapshot
|
|
545
|
-
|| !sameIdentity(item.snapshot.identity, current.identity)
|
|
546
|
-
|| item.snapshot.digest !== current.digest) {
|
|
547
|
-
conflict ||= initializationSnapshotConflict(item.relative, 'rollback');
|
|
548
|
-
continue;
|
|
549
|
-
}
|
|
550
|
-
const before = captureDirectoryChain(
|
|
551
|
-
root,
|
|
552
|
-
path.dirname(item.target),
|
|
553
|
-
item.relative,
|
|
554
|
-
'rollback',
|
|
555
|
-
knownDirectories,
|
|
556
|
-
);
|
|
557
|
-
fs.unlinkSync(item.target);
|
|
558
|
-
const after = captureDirectoryChain(
|
|
559
|
-
root,
|
|
560
|
-
path.dirname(item.target),
|
|
561
|
-
item.relative,
|
|
562
|
-
'rollback',
|
|
563
|
-
knownDirectories,
|
|
564
|
-
);
|
|
565
|
-
assertSameDirectoryChain(before, after, item.relative, 'rollback');
|
|
566
|
-
} catch (error) {
|
|
567
|
-
conflict ||= error.code === 'initialization_snapshot_changed'
|
|
568
|
-
? error
|
|
569
|
-
: initializationSnapshotConflict(item.relative, 'rollback');
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
for (const item of [...createdDirectories].reverse()) {
|
|
573
|
-
try {
|
|
574
|
-
const entry = fs.lstatSync(item.path, { bigint: true });
|
|
575
|
-
const resolved = fs.realpathSync.native(item.path);
|
|
576
|
-
if (!entry.isDirectory()
|
|
577
|
-
|| entry.isSymbolicLink()
|
|
578
|
-
|| resolved !== item.snapshot.resolved
|
|
579
|
-
|| !sameIdentity(directoryIdentity(entry), item.snapshot.identity)) {
|
|
580
|
-
conflict ||= initializationSnapshotConflict(item.relative, 'rollback');
|
|
581
|
-
continue;
|
|
582
|
-
}
|
|
583
|
-
fs.rmdirSync(item.path);
|
|
584
|
-
} catch (error) {
|
|
585
|
-
conflict ||= initializationSnapshotConflict(item.relative, 'rollback');
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
return conflict;
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
function isInitializationConflict(error) {
|
|
592
|
-
return error.code === 'preserved_north_star_changed'
|
|
593
|
-
|| error.code === 'initialization_snapshot_changed';
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
function isTypedInitializationError(error) {
|
|
597
|
-
return error?.code === 'preserved_north_star_changed'
|
|
598
|
-
|| error?.code === 'initialization_snapshot_changed'
|
|
599
|
-
|| error?.code === 'initialization_snapshot_io_error'
|
|
600
|
-
|| error?.code === 'initialization_cleanup_conflict'
|
|
601
|
-
|| error?.code === 'north_star_template_invalid';
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
function initializationErrorPayload(error, includeSchema = true) {
|
|
605
|
-
const payload = {
|
|
606
|
-
code: error.code,
|
|
607
|
-
retryable: error.retryable,
|
|
608
|
-
phase: error.phase,
|
|
609
|
-
path: error.path,
|
|
610
|
-
message: error.message,
|
|
611
|
-
};
|
|
612
|
-
if (includeSchema) payload.$schema = 'ultra-init-error-v1';
|
|
613
|
-
if (error.operation) payload.operation = error.operation;
|
|
614
|
-
if (error.errno) payload.errno = error.errno;
|
|
615
|
-
if (Array.isArray(error.diagnostics)) payload.diagnostics = error.diagnostics;
|
|
616
|
-
if (error.recovery) payload.recovery = error.recovery;
|
|
617
|
-
if (error.cleanupConflict) {
|
|
618
|
-
payload.cleanup_conflict = initializationErrorPayload(error.cleanupConflict, false);
|
|
619
|
-
}
|
|
620
|
-
return payload;
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
function assertPublishedFilesUnchanged(published, phase) {
|
|
624
|
-
for (const item of published) {
|
|
625
|
-
assertFileSnapshotUnchanged(
|
|
626
|
-
item.target,
|
|
627
|
-
item.relative,
|
|
628
|
-
item.snapshot,
|
|
629
|
-
phase,
|
|
630
|
-
);
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
function publishStagedFiles(
|
|
635
|
-
stage,
|
|
636
|
-
ultra,
|
|
637
|
-
relativeFiles,
|
|
638
|
-
stagedSnapshots,
|
|
639
|
-
root,
|
|
640
|
-
knownDirectories,
|
|
641
|
-
) {
|
|
642
|
-
const published = [];
|
|
643
|
-
const createdDirectories = [];
|
|
644
|
-
try {
|
|
645
|
-
for (const relative of relativeFiles) {
|
|
646
|
-
const target = path.join(ultra, relative);
|
|
647
|
-
const staged = path.join(stage, relative);
|
|
648
|
-
const expected = stagedSnapshots.get(relative);
|
|
649
|
-
ensurePublishDirectory(
|
|
650
|
-
path.dirname(target),
|
|
651
|
-
ultra,
|
|
652
|
-
createdDirectories,
|
|
653
|
-
root,
|
|
654
|
-
knownDirectories,
|
|
655
|
-
relative,
|
|
656
|
-
);
|
|
657
|
-
assertFileSnapshotUnchanged(staged, relative, expected, 'before_publish');
|
|
658
|
-
const beforeParent = captureDirectoryChain(
|
|
659
|
-
root,
|
|
660
|
-
path.dirname(target),
|
|
661
|
-
relative,
|
|
662
|
-
'publish_parent_before',
|
|
663
|
-
knownDirectories,
|
|
664
|
-
);
|
|
665
|
-
try {
|
|
666
|
-
fs.linkSync(staged, target);
|
|
667
|
-
} catch {
|
|
668
|
-
throw initializationSnapshotConflict(relative, 'publish');
|
|
669
|
-
}
|
|
670
|
-
const item = { target, relative, snapshot: null };
|
|
671
|
-
published.push(item);
|
|
672
|
-
let observed = stableFileSnapshot(target, relative, 'publish_verify');
|
|
673
|
-
if (!sameIdentity(objectIdentity(expected.identity), objectIdentity(observed.identity))
|
|
674
|
-
|| expected.digest !== observed.digest) {
|
|
675
|
-
throw initializationSnapshotConflict(relative, 'publish_verify');
|
|
676
|
-
}
|
|
677
|
-
item.snapshot = observed;
|
|
678
|
-
try {
|
|
679
|
-
fs.unlinkSync(staged);
|
|
680
|
-
} catch {
|
|
681
|
-
throw initializationSnapshotConflict(relative, 'publish_commit');
|
|
682
|
-
}
|
|
683
|
-
observed = stableFileSnapshot(target, relative, 'publish_verify');
|
|
684
|
-
if (!sameIdentity(objectIdentity(expected.identity), objectIdentity(observed.identity))
|
|
685
|
-
|| expected.digest !== observed.digest) {
|
|
686
|
-
throw initializationSnapshotConflict(relative, 'publish_verify');
|
|
687
|
-
}
|
|
688
|
-
item.snapshot = observed;
|
|
689
|
-
const afterParent = captureDirectoryChain(
|
|
690
|
-
root,
|
|
691
|
-
path.dirname(target),
|
|
692
|
-
relative,
|
|
693
|
-
'publish_parent_after',
|
|
694
|
-
knownDirectories,
|
|
695
|
-
);
|
|
696
|
-
assertSameDirectoryChain(
|
|
697
|
-
beforeParent,
|
|
698
|
-
afterParent,
|
|
699
|
-
relative,
|
|
700
|
-
'publish_parent_after',
|
|
701
|
-
);
|
|
702
|
-
}
|
|
703
|
-
assertPublishedFilesUnchanged(published, 'after_publish');
|
|
704
|
-
} catch (error) {
|
|
705
|
-
const rollbackConflict = rollbackPublishedFiles(
|
|
706
|
-
published,
|
|
707
|
-
createdDirectories,
|
|
708
|
-
root,
|
|
709
|
-
knownDirectories,
|
|
710
|
-
);
|
|
711
|
-
if (rollbackConflict && !isInitializationConflict(error)) throw rollbackConflict;
|
|
712
|
-
throw error;
|
|
713
|
-
}
|
|
714
|
-
return { published, createdDirectories };
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
function captureStagedDirectories(stage, ultra, root) {
|
|
718
|
-
const directories = [];
|
|
719
|
-
(function walk(directory) {
|
|
720
|
-
const relative = path.relative(stage, directory);
|
|
721
|
-
const entry = fs.lstatSync(directory, { bigint: true });
|
|
722
|
-
if (!entry.isDirectory() || entry.isSymbolicLink()) {
|
|
723
|
-
throw initializationSnapshotConflict(relative || '.ultra', 'before_publish');
|
|
724
|
-
}
|
|
725
|
-
directories.push({
|
|
726
|
-
path: path.join(ultra, relative),
|
|
727
|
-
relative: relative || '.',
|
|
728
|
-
snapshot: {
|
|
729
|
-
resolved: path.join(root.resolved, '.ultra', relative),
|
|
730
|
-
identity: directoryIdentity(entry),
|
|
731
|
-
},
|
|
732
|
-
});
|
|
733
|
-
for (const child of fs.readdirSync(directory, { withFileTypes: true })) {
|
|
734
|
-
if (child.isDirectory()) walk(path.join(directory, child.name));
|
|
735
|
-
}
|
|
736
|
-
}(stage));
|
|
737
|
-
return directories;
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
function assertPathAbsent(file, relative, phase) {
|
|
741
|
-
try {
|
|
742
|
-
fs.lstatSync(file);
|
|
743
|
-
} catch (error) {
|
|
744
|
-
if (error.code === 'ENOENT') return;
|
|
745
|
-
throw initializationSnapshotConflict(relative, phase);
|
|
746
|
-
}
|
|
747
|
-
throw initializationSnapshotConflict(relative, phase);
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
function pathExistsWithoutFollowing(file) {
|
|
751
|
-
try {
|
|
752
|
-
fs.lstatSync(file);
|
|
753
|
-
return true;
|
|
754
|
-
} catch (error) {
|
|
755
|
-
if (error.code === 'ENOENT') return false;
|
|
756
|
-
return true;
|
|
757
|
-
}
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
function cleanupOwnedStage(stage, expected, root, knownDirectories) {
|
|
761
|
-
let current;
|
|
762
|
-
try {
|
|
763
|
-
const chain = captureDirectoryChain(
|
|
764
|
-
root,
|
|
765
|
-
stage,
|
|
766
|
-
path.basename(stage),
|
|
767
|
-
'stage_cleanup',
|
|
768
|
-
knownDirectories,
|
|
769
|
-
);
|
|
770
|
-
current = chain[chain.length - 1];
|
|
771
|
-
if (current.resolved !== expected.resolved
|
|
772
|
-
|| !sameIdentity(current.identity, expected.identity)) {
|
|
773
|
-
throw initializationSnapshotConflict(path.basename(stage), 'stage_cleanup');
|
|
774
|
-
}
|
|
775
|
-
} catch {
|
|
776
|
-
throw stageCleanupConflict(stage, expected, {
|
|
777
|
-
currentPathPreserved: pathExistsWithoutFollowing(stage),
|
|
778
|
-
});
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
try {
|
|
782
|
-
fs.rmSync(stage, { recursive: true });
|
|
783
|
-
} catch {
|
|
784
|
-
throw stageCleanupConflict(stage, expected, {
|
|
785
|
-
currentPathPreserved: pathExistsWithoutFollowing(stage),
|
|
786
|
-
destructiveCleanupAttempted: true,
|
|
787
|
-
});
|
|
788
|
-
}
|
|
789
|
-
}
|
|
790
|
-
|
|
791
|
-
function initialize(project) {
|
|
792
|
-
if (!fs.existsSync(project)) throw new Error(`project does not exist: ${project}`);
|
|
793
|
-
ensurePlainDirectory(project, 'project');
|
|
794
|
-
const root = captureRoot(project);
|
|
795
|
-
const knownDirectories = new Map([[
|
|
796
|
-
project,
|
|
797
|
-
{ path: project, resolved: root.resolved, identity: root.identity },
|
|
798
|
-
]]);
|
|
799
|
-
const source = templateRoot();
|
|
800
|
-
const ultra = path.join(project, '.ultra');
|
|
801
|
-
const ultraExists = fs.existsSync(ultra);
|
|
802
|
-
if (ultraExists) {
|
|
803
|
-
ensurePlainDirectory(ultra, '.ultra');
|
|
804
|
-
captureDirectoryChain(root, ultra, '.ultra', 'initialization_snapshot', knownDirectories);
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
const templateFiles = listFiles(source);
|
|
808
|
-
const preserved = ultraExists
|
|
809
|
-
? templateFiles.filter((relative) => fs.existsSync(path.join(ultra, relative)))
|
|
810
|
-
: [];
|
|
811
|
-
const created = templateFiles.filter((relative) => !preserved.includes(relative));
|
|
812
|
-
const preservedSnapshots = new Map(preserved.map((relative) => [
|
|
813
|
-
relative,
|
|
814
|
-
stableRepositoryFileSnapshot(
|
|
815
|
-
root,
|
|
816
|
-
ultra,
|
|
817
|
-
relative,
|
|
818
|
-
'classification',
|
|
819
|
-
knownDirectories,
|
|
820
|
-
{
|
|
821
|
-
preservedNorthStar: relative === 'north-star.md',
|
|
822
|
-
retainBytes: relative === 'north-star.md',
|
|
823
|
-
},
|
|
824
|
-
),
|
|
825
|
-
]));
|
|
826
|
-
const northStarPath = path.join(ultra, 'north-star.md');
|
|
827
|
-
const northStarSnapshot = preservedSnapshots.get('north-star.md') || null;
|
|
828
|
-
let northStar = created.includes('north-star.md')
|
|
829
|
-
? { disposition: 'created_unresearched' }
|
|
830
|
-
: classifyPreservedNorthStar(northStarPath, northStarSnapshot);
|
|
831
|
-
|
|
832
|
-
assertPreservedFilesUnchanged(
|
|
833
|
-
root,
|
|
834
|
-
ultra,
|
|
835
|
-
preservedSnapshots,
|
|
836
|
-
'before_stage',
|
|
837
|
-
knownDirectories,
|
|
838
|
-
);
|
|
839
|
-
|
|
840
|
-
let stage = fs.mkdtempSync(path.join(project, '.ultra-init-'));
|
|
841
|
-
const stageChain = captureDirectoryChain(
|
|
842
|
-
root,
|
|
843
|
-
stage,
|
|
844
|
-
path.basename(stage),
|
|
845
|
-
'stage_snapshot',
|
|
846
|
-
knownDirectories,
|
|
847
|
-
);
|
|
848
|
-
const stageSnapshot = stageChain[stageChain.length - 1];
|
|
849
|
-
let publication = null;
|
|
850
|
-
let pendingError = null;
|
|
851
|
-
try {
|
|
852
|
-
const stagedSnapshots = stageFiles(source, created, stage);
|
|
853
|
-
if (created.includes('north-star.md')) {
|
|
854
|
-
validateNewNorthStar(path.join(stage, 'north-star.md'));
|
|
855
|
-
}
|
|
856
|
-
assertPreservedFilesUnchanged(
|
|
857
|
-
root,
|
|
858
|
-
ultra,
|
|
859
|
-
preservedSnapshots,
|
|
860
|
-
'before_publish',
|
|
861
|
-
knownDirectories,
|
|
862
|
-
);
|
|
863
|
-
|
|
864
|
-
if (!ultraExists) {
|
|
865
|
-
for (const [relative, expected] of stagedSnapshots) {
|
|
866
|
-
assertFileSnapshotUnchanged(
|
|
867
|
-
path.join(stage, relative),
|
|
868
|
-
relative,
|
|
869
|
-
expected,
|
|
870
|
-
'before_publish',
|
|
871
|
-
);
|
|
872
|
-
}
|
|
873
|
-
const beforeParent = captureDirectoryChain(
|
|
874
|
-
root,
|
|
875
|
-
project,
|
|
876
|
-
'.ultra',
|
|
877
|
-
'publish_parent_before',
|
|
878
|
-
knownDirectories,
|
|
879
|
-
);
|
|
880
|
-
assertPathAbsent(ultra, '.ultra', 'before_publish');
|
|
881
|
-
const createdDirectories = captureStagedDirectories(stage, ultra, root);
|
|
882
|
-
try {
|
|
883
|
-
fs.renameSync(stage, ultra);
|
|
884
|
-
} catch {
|
|
885
|
-
throw initializationSnapshotConflict('.ultra', 'publish');
|
|
886
|
-
}
|
|
887
|
-
stage = null;
|
|
888
|
-
publication = {
|
|
889
|
-
published: [...stagedSnapshots].map(([relative, snapshot]) => ({
|
|
890
|
-
target: path.join(ultra, relative),
|
|
891
|
-
relative,
|
|
892
|
-
snapshot,
|
|
893
|
-
})),
|
|
894
|
-
createdDirectories,
|
|
895
|
-
};
|
|
896
|
-
const afterParent = captureDirectoryChain(
|
|
897
|
-
root,
|
|
898
|
-
project,
|
|
899
|
-
'.ultra',
|
|
900
|
-
'publish_parent_after',
|
|
901
|
-
knownDirectories,
|
|
902
|
-
);
|
|
903
|
-
assertSameDirectoryChain(
|
|
904
|
-
beforeParent,
|
|
905
|
-
afterParent,
|
|
906
|
-
'.ultra',
|
|
907
|
-
'publish_parent_after',
|
|
908
|
-
);
|
|
909
|
-
const ultraChain = captureDirectoryChain(
|
|
910
|
-
root,
|
|
911
|
-
ultra,
|
|
912
|
-
'.ultra',
|
|
913
|
-
'publish_parent_after',
|
|
914
|
-
knownDirectories,
|
|
915
|
-
);
|
|
916
|
-
const expectedUltra = createdDirectories[0].snapshot;
|
|
917
|
-
const observedUltra = ultraChain[ultraChain.length - 1];
|
|
918
|
-
if (expectedUltra.resolved !== observedUltra.resolved
|
|
919
|
-
|| !sameIdentity(expectedUltra.identity, observedUltra.identity)) {
|
|
920
|
-
throw initializationSnapshotConflict('.ultra', 'publish_parent_after');
|
|
921
|
-
}
|
|
922
|
-
for (const item of publication.published) {
|
|
923
|
-
const { relative } = item;
|
|
924
|
-
const expected = stagedSnapshots.get(relative);
|
|
925
|
-
const beforeFileParent = captureDirectoryChain(
|
|
926
|
-
root,
|
|
927
|
-
path.dirname(item.target),
|
|
928
|
-
relative,
|
|
929
|
-
'publish_parent_before',
|
|
930
|
-
knownDirectories,
|
|
931
|
-
);
|
|
932
|
-
const observed = stableFileSnapshot(
|
|
933
|
-
item.target,
|
|
934
|
-
relative,
|
|
935
|
-
'after_publish',
|
|
936
|
-
);
|
|
937
|
-
if (!sameIdentity(objectIdentity(expected.identity), objectIdentity(observed.identity))
|
|
938
|
-
|| expected.digest !== observed.digest) {
|
|
939
|
-
throw initializationSnapshotConflict(relative, 'after_publish');
|
|
940
|
-
}
|
|
941
|
-
item.snapshot = observed;
|
|
942
|
-
const afterFileParent = captureDirectoryChain(
|
|
943
|
-
root,
|
|
944
|
-
path.dirname(item.target),
|
|
945
|
-
relative,
|
|
946
|
-
'publish_parent_after',
|
|
947
|
-
knownDirectories,
|
|
948
|
-
);
|
|
949
|
-
assertSameDirectoryChain(
|
|
950
|
-
beforeFileParent,
|
|
951
|
-
afterFileParent,
|
|
952
|
-
relative,
|
|
953
|
-
'publish_parent_after',
|
|
954
|
-
);
|
|
955
|
-
}
|
|
956
|
-
assertPublishedFilesUnchanged(publication.published, 'after_publish');
|
|
957
|
-
} else {
|
|
958
|
-
publication = publishStagedFiles(
|
|
959
|
-
stage,
|
|
960
|
-
ultra,
|
|
961
|
-
created,
|
|
962
|
-
stagedSnapshots,
|
|
963
|
-
root,
|
|
964
|
-
knownDirectories,
|
|
965
|
-
);
|
|
966
|
-
assertPreservedFilesUnchanged(
|
|
967
|
-
root,
|
|
968
|
-
ultra,
|
|
969
|
-
preservedSnapshots,
|
|
970
|
-
'after_publish',
|
|
971
|
-
knownDirectories,
|
|
972
|
-
);
|
|
973
|
-
assertPublishedFilesUnchanged(publication.published, 'after_publish');
|
|
974
|
-
}
|
|
975
|
-
} catch (error) {
|
|
976
|
-
pendingError = error;
|
|
977
|
-
if (publication) {
|
|
978
|
-
const rollbackConflict = rollbackPublishedFiles(
|
|
979
|
-
publication.published,
|
|
980
|
-
publication.createdDirectories,
|
|
981
|
-
root,
|
|
982
|
-
knownDirectories,
|
|
983
|
-
);
|
|
984
|
-
if (rollbackConflict && !isInitializationConflict(error)) {
|
|
985
|
-
pendingError = rollbackConflict;
|
|
986
|
-
}
|
|
987
|
-
}
|
|
988
|
-
} finally {
|
|
989
|
-
if (stage) {
|
|
990
|
-
try {
|
|
991
|
-
cleanupOwnedStage(stage, stageSnapshot, root, knownDirectories);
|
|
992
|
-
} catch (cleanupError) {
|
|
993
|
-
if (pendingError && isTypedInitializationError(pendingError)) {
|
|
994
|
-
pendingError.cleanupConflict = cleanupError;
|
|
995
|
-
} else {
|
|
996
|
-
throw cleanupError;
|
|
997
|
-
}
|
|
998
|
-
}
|
|
999
|
-
}
|
|
1000
|
-
}
|
|
1001
|
-
if (pendingError) throw pendingError;
|
|
1002
|
-
return {
|
|
1003
|
-
$schema: 'ultra-init-result-v1',
|
|
1004
|
-
project,
|
|
1005
|
-
template: source,
|
|
1006
|
-
created,
|
|
1007
|
-
preserved,
|
|
1008
|
-
north_star: {
|
|
1009
|
-
path: 'north-star.md',
|
|
1010
|
-
...northStar,
|
|
1011
|
-
},
|
|
1012
|
-
};
|
|
1013
|
-
}
|
|
1014
|
-
|
|
1015
|
-
if (require.main === module) {
|
|
1016
|
-
try {
|
|
1017
|
-
process.stdout.write(`${JSON.stringify(initialize(parseArgs(process.argv.slice(2))), null, 2)}\n`);
|
|
1018
|
-
} catch (error) {
|
|
1019
|
-
if (isTypedInitializationError(error)) {
|
|
1020
|
-
process.stderr.write(`${JSON.stringify(initializationErrorPayload(error), null, 2)}\n`);
|
|
1021
|
-
} else {
|
|
1022
|
-
process.stderr.write(`${error.message}\n`);
|
|
1023
|
-
}
|
|
1024
|
-
process.exitCode = 1;
|
|
1025
|
-
}
|
|
1026
|
-
}
|
|
1027
|
-
|
|
1028
|
-
module.exports = {
|
|
1029
|
-
assertFileSnapshotUnchanged,
|
|
1030
|
-
stableFileSnapshot,
|
|
1031
|
-
};
|