owlrunkit 0.22.1 → 0.23.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 +21 -0
- package/README.md +37 -9
- package/integrations/codex/skills/owlcoda-runkit/SKILL.md +34 -12
- package/integrations/codex/skills/owlcoda-runkit/assets/templates/config.json +3 -3
- package/package.json +1 -1
- package/packages/attest/distribution-manifest-v1.json +5 -0
- package/packages/attest/src/quick.mjs +5 -0
- package/registry-release-policy-v2.json +5 -5
- package/sbom.spdx.json +4 -4
- package/scripts/runkit-contract/core-contract.mjs +5 -1
- package/scripts/runkit-contract/formal-workflow.mjs +1 -1
- package/scripts/runkit-contract/project-cli-resolver.mjs +4 -0
- package/scripts/runkit-contract/project-completion-continuity.mjs +545 -0
- package/scripts/runkit-contract/registry-adoption.mjs +4 -4
- package/scripts/runkit-contract/runkit-cli.mjs +41 -5
- package/scripts/runkit-contract/schemas/fleet-skill-upgrade-plan-v1.schema.json +1 -1
- package/scripts/runkit-contract/schemas/project-completion-continuity-v1.schema.json +144 -0
- package/scripts/runkit-contract/schemas/successor-scaffold-v1.schema.json +184 -0
- package/scripts/runkit-contract/schemas/team-project-status-v5.schema.json +169 -0
|
@@ -0,0 +1,545 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import {
|
|
3
|
+
lstatSync,
|
|
4
|
+
readFileSync,
|
|
5
|
+
realpathSync,
|
|
6
|
+
} from "node:fs";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
quickSnapshotFingerprintValid,
|
|
11
|
+
validWorkspaceSnapshotShape,
|
|
12
|
+
} from "../../packages/attest/src/quick-receipt-contract.mjs";
|
|
13
|
+
import { captureWorkspaceSnapshot } from "./quick-workspace-snapshot.mjs";
|
|
14
|
+
import { readTeamProjectStatusV4 } from "./team-project.mjs";
|
|
15
|
+
|
|
16
|
+
const AUTHORITY_BOUNDARY = Object.freeze({
|
|
17
|
+
agentDispatch: false,
|
|
18
|
+
git: false,
|
|
19
|
+
release: false,
|
|
20
|
+
deployment: false,
|
|
21
|
+
production: false,
|
|
22
|
+
business: false,
|
|
23
|
+
automation: false,
|
|
24
|
+
money: false,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const MAX_CHANGED_PATHS = 256;
|
|
28
|
+
|
|
29
|
+
function canonical(value) {
|
|
30
|
+
if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
|
|
31
|
+
if (value && typeof value === "object") {
|
|
32
|
+
return `{${Object.keys(value).sort().map(key => (
|
|
33
|
+
`${JSON.stringify(key)}:${canonical(value[key])}`
|
|
34
|
+
)).join(",")}}`;
|
|
35
|
+
}
|
|
36
|
+
const encoded = JSON.stringify(value);
|
|
37
|
+
if (encoded === undefined) throw new Error("Completion continuity value is not canonical JSON.");
|
|
38
|
+
return encoded;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function compareCodeUnits(left, right) {
|
|
42
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function uniqueSorted(values) {
|
|
46
|
+
return [...new Set(values)].sort(compareCodeUnits);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function canonicalGitRoot(workspaceRoot) {
|
|
50
|
+
const requested = path.resolve(workspaceRoot);
|
|
51
|
+
const stat = lstatSync(requested);
|
|
52
|
+
const resolved = realpathSync(requested);
|
|
53
|
+
if (stat.isSymbolicLink() || !stat.isDirectory() || requested !== resolved) {
|
|
54
|
+
throw new Error("Successor source workspace must be a real directory without symlink traversal.");
|
|
55
|
+
}
|
|
56
|
+
const gitRoot = realpathSync(execFileSync(
|
|
57
|
+
"git",
|
|
58
|
+
["-C", resolved, "rev-parse", "--show-toplevel"],
|
|
59
|
+
{
|
|
60
|
+
encoding: "utf8",
|
|
61
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
62
|
+
env: { ...process.env, GIT_OPTIONAL_LOCKS: "0" },
|
|
63
|
+
},
|
|
64
|
+
).trim());
|
|
65
|
+
if (gitRoot !== resolved) {
|
|
66
|
+
throw new Error(`Successor source workspace must be the exact Git worktree root: ${gitRoot}.`);
|
|
67
|
+
}
|
|
68
|
+
return resolved;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function stableWorkspaceSnapshot(workspaceRoot) {
|
|
72
|
+
const before = captureWorkspaceSnapshot(workspaceRoot);
|
|
73
|
+
const after = captureWorkspaceSnapshot(workspaceRoot);
|
|
74
|
+
if (canonical(before) !== canonical(after)) {
|
|
75
|
+
throw new Error("Successor source workspace changed while its read-only snapshot was captured.");
|
|
76
|
+
}
|
|
77
|
+
return after;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function readBaselineEvent(controllerRoot, delivery) {
|
|
81
|
+
const runtimeRoot = path.join(controllerRoot, ".owlcoda", "runkit");
|
|
82
|
+
const requested = path.resolve(runtimeRoot, delivery.truthRef);
|
|
83
|
+
const relative = path.relative(runtimeRoot, requested);
|
|
84
|
+
if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
|
|
85
|
+
throw new Error("Completion continuity source delivery truth ref escapes the RunKit root.");
|
|
86
|
+
}
|
|
87
|
+
const stat = lstatSync(requested);
|
|
88
|
+
if (stat.isSymbolicLink() || !stat.isFile() || realpathSync(requested) !== requested) {
|
|
89
|
+
throw new Error("Completion continuity source delivery must be a regular file without symlinks.");
|
|
90
|
+
}
|
|
91
|
+
const event = JSON.parse(readFileSync(requested, "utf8"));
|
|
92
|
+
if (event.type !== "source_delivery_imported"
|
|
93
|
+
|| event.deliveryId !== delivery.deliveryId
|
|
94
|
+
|| event.workspaceSnapshotFingerprint !== delivery.workspaceSnapshotFingerprint
|
|
95
|
+
|| !validWorkspaceSnapshotShape(event.targetSnapshot)
|
|
96
|
+
|| !quickSnapshotFingerprintValid(event.targetSnapshot)
|
|
97
|
+
|| event.targetSnapshot.sourceFingerprint !== delivery.workspaceSnapshotFingerprint) {
|
|
98
|
+
throw new Error("Completion continuity source delivery baseline is invalid.");
|
|
99
|
+
}
|
|
100
|
+
return event;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function changedOverlayPaths(before, after) {
|
|
104
|
+
const beforeRows = new Map(before.dirtyOverlay.map(row => [row.path, canonical(row)]));
|
|
105
|
+
const afterRows = new Map(after.dirtyOverlay.map(row => [row.path, canonical(row)]));
|
|
106
|
+
return uniqueSorted([...beforeRows.keys(), ...afterRows.keys()].filter(filePath => (
|
|
107
|
+
beforeRows.get(filePath) !== afterRows.get(filePath)
|
|
108
|
+
)));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function committedPaths(root, beforeHead, afterHead) {
|
|
112
|
+
if (beforeHead === afterHead) return { paths: [], complete: true };
|
|
113
|
+
if (beforeHead === null || afterHead === null) return { paths: [], complete: false };
|
|
114
|
+
try {
|
|
115
|
+
const raw = execFileSync(
|
|
116
|
+
"git",
|
|
117
|
+
[
|
|
118
|
+
"-C", root, "diff", "--name-status", "-z", "--find-renames",
|
|
119
|
+
beforeHead, afterHead, "--",
|
|
120
|
+
],
|
|
121
|
+
{
|
|
122
|
+
encoding: "utf8",
|
|
123
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
124
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
125
|
+
env: { ...process.env, GIT_OPTIONAL_LOCKS: "0" },
|
|
126
|
+
},
|
|
127
|
+
);
|
|
128
|
+
const fields = raw.split("\0");
|
|
129
|
+
if (fields.at(-1) === "") fields.pop();
|
|
130
|
+
const paths = [];
|
|
131
|
+
for (let index = 0; index < fields.length;) {
|
|
132
|
+
const status = fields[index];
|
|
133
|
+
index += 1;
|
|
134
|
+
if (!/^[A-Z][0-9]*$/u.test(status ?? "") || !fields[index]) {
|
|
135
|
+
throw new Error("Committed source delta is not valid Git name-status output.");
|
|
136
|
+
}
|
|
137
|
+
paths.push(fields[index]);
|
|
138
|
+
index += 1;
|
|
139
|
+
if (status.startsWith("R") || status.startsWith("C")) {
|
|
140
|
+
if (!fields[index]) {
|
|
141
|
+
throw new Error("Committed rename or copy is missing its destination path.");
|
|
142
|
+
}
|
|
143
|
+
paths.push(fields[index]);
|
|
144
|
+
index += 1;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return {
|
|
148
|
+
paths: uniqueSorted(paths),
|
|
149
|
+
complete: true,
|
|
150
|
+
};
|
|
151
|
+
} catch {
|
|
152
|
+
return { paths: [], complete: false };
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function dependencyPaths(before, after) {
|
|
157
|
+
const beforeRows = new Map(before.dependencyLockfiles.map(row => [row.path, row.sha256]));
|
|
158
|
+
const afterRows = new Map(after.dependencyLockfiles.map(row => [row.path, row.sha256]));
|
|
159
|
+
return uniqueSorted([...beforeRows.keys(), ...afterRows.keys()].filter(filePath => (
|
|
160
|
+
beforeRows.get(filePath) !== afterRows.get(filePath)
|
|
161
|
+
)));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function sourceDelta(root, baseline, current) {
|
|
165
|
+
const committed = committedPaths(root, baseline.headCommit, current.headCommit);
|
|
166
|
+
const overlayPaths = changedOverlayPaths(baseline, current);
|
|
167
|
+
const dependencyEnvironmentPaths = dependencyPaths(baseline, current);
|
|
168
|
+
const allPaths = uniqueSorted([
|
|
169
|
+
...committed.paths,
|
|
170
|
+
...overlayPaths,
|
|
171
|
+
...dependencyEnvironmentPaths,
|
|
172
|
+
]);
|
|
173
|
+
return {
|
|
174
|
+
baselineHead: baseline.headCommit,
|
|
175
|
+
currentHead: current.headCommit,
|
|
176
|
+
headChanged: baseline.headCommit !== current.headCommit,
|
|
177
|
+
trackedTreeChanged: baseline.trackedTreeIdentity !== current.trackedTreeIdentity,
|
|
178
|
+
submodulesChanged: canonical(baseline.submodules) !== canonical(current.submodules),
|
|
179
|
+
committedPaths: committed.paths.slice(0, MAX_CHANGED_PATHS),
|
|
180
|
+
overlayPaths: overlayPaths.slice(0, MAX_CHANGED_PATHS),
|
|
181
|
+
dependencyEnvironmentPaths: dependencyEnvironmentPaths.slice(0, MAX_CHANGED_PATHS),
|
|
182
|
+
changedPaths: allPaths.slice(0, MAX_CHANGED_PATHS),
|
|
183
|
+
changedPathCount: allPaths.length,
|
|
184
|
+
changedPathsTruncated: allPaths.length > MAX_CHANGED_PATHS,
|
|
185
|
+
pathEvidenceComplete: committed.complete,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function baselineDelivery(status, targetRoot) {
|
|
190
|
+
return [...status.sourceDeliveries]
|
|
191
|
+
.filter(delivery => path.resolve(delivery.targetWorkspaceRef) === targetRoot)
|
|
192
|
+
.sort((left, right) => (
|
|
193
|
+
compareCodeUnits(left.occurredAt, right.occurredAt)
|
|
194
|
+
|| compareCodeUnits(left.deliveryId, right.deliveryId)
|
|
195
|
+
))
|
|
196
|
+
.at(-1) ?? null;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function continuityProjection({ controllerRoot, status, targetWorkspaceRoot = null }) {
|
|
200
|
+
if (status.overall !== "completed") {
|
|
201
|
+
return {
|
|
202
|
+
schemaVersion: "OwlCodaRunKitProjectCompletionContinuityV1",
|
|
203
|
+
status: "not_applicable",
|
|
204
|
+
projectId: status.projectId,
|
|
205
|
+
projectTruthHash: status.projectTruthHash,
|
|
206
|
+
disposition: "project_work_ledger_incomplete",
|
|
207
|
+
targetWorkspaceRef: null,
|
|
208
|
+
baseline: null,
|
|
209
|
+
current: null,
|
|
210
|
+
delta: null,
|
|
211
|
+
nextAction: null,
|
|
212
|
+
readOnly: true,
|
|
213
|
+
writesPerformed: false,
|
|
214
|
+
authorityBoundary: { ...AUTHORITY_BOUNDARY },
|
|
215
|
+
authorizationGranted: false,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const requestedTarget = targetWorkspaceRoot === null
|
|
220
|
+
? status.sourceDeliveries.at(-1)?.targetWorkspaceRef ?? null
|
|
221
|
+
: targetWorkspaceRoot;
|
|
222
|
+
if (requestedTarget === null) {
|
|
223
|
+
return {
|
|
224
|
+
schemaVersion: "OwlCodaRunKitProjectCompletionContinuityV1",
|
|
225
|
+
status: "baseline_missing",
|
|
226
|
+
projectId: status.projectId,
|
|
227
|
+
projectTruthHash: status.projectTruthHash,
|
|
228
|
+
disposition: "completed_project_source_baseline_missing",
|
|
229
|
+
targetWorkspaceRef: null,
|
|
230
|
+
baseline: null,
|
|
231
|
+
current: null,
|
|
232
|
+
delta: null,
|
|
233
|
+
nextAction: "Review the current workspace and bind an explicit source baseline before deciding whether the project is terminal.",
|
|
234
|
+
readOnly: true,
|
|
235
|
+
writesPerformed: false,
|
|
236
|
+
authorityBoundary: { ...AUTHORITY_BOUNDARY },
|
|
237
|
+
authorizationGranted: false,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
let targetRoot;
|
|
242
|
+
try {
|
|
243
|
+
targetRoot = canonicalGitRoot(requestedTarget);
|
|
244
|
+
} catch {
|
|
245
|
+
return {
|
|
246
|
+
schemaVersion: "OwlCodaRunKitProjectCompletionContinuityV1",
|
|
247
|
+
status: "source_unverifiable",
|
|
248
|
+
projectId: status.projectId,
|
|
249
|
+
projectTruthHash: status.projectTruthHash,
|
|
250
|
+
disposition: "completed_project_source_unverifiable",
|
|
251
|
+
targetWorkspaceRef: path.resolve(requestedTarget),
|
|
252
|
+
baseline: null,
|
|
253
|
+
current: null,
|
|
254
|
+
delta: null,
|
|
255
|
+
nextAction: "Restore or explicitly replace the modeled source workspace before deciding whether the project is terminal.",
|
|
256
|
+
readOnly: true,
|
|
257
|
+
writesPerformed: false,
|
|
258
|
+
authorityBoundary: { ...AUTHORITY_BOUNDARY },
|
|
259
|
+
authorizationGranted: false,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const delivery = baselineDelivery(status, targetRoot);
|
|
264
|
+
if (delivery === null) {
|
|
265
|
+
const current = stableWorkspaceSnapshot(targetRoot);
|
|
266
|
+
return {
|
|
267
|
+
schemaVersion: "OwlCodaRunKitProjectCompletionContinuityV1",
|
|
268
|
+
status: "baseline_missing",
|
|
269
|
+
projectId: status.projectId,
|
|
270
|
+
projectTruthHash: status.projectTruthHash,
|
|
271
|
+
disposition: "completed_project_source_baseline_missing",
|
|
272
|
+
targetWorkspaceRef: targetRoot,
|
|
273
|
+
baseline: null,
|
|
274
|
+
current: {
|
|
275
|
+
headCommit: current.headCommit,
|
|
276
|
+
workspaceSnapshotFingerprint: current.sourceFingerprint,
|
|
277
|
+
},
|
|
278
|
+
delta: null,
|
|
279
|
+
nextAction: "Review the current workspace and bind an explicit source baseline before deciding whether the project is terminal.",
|
|
280
|
+
readOnly: true,
|
|
281
|
+
writesPerformed: false,
|
|
282
|
+
authorityBoundary: { ...AUTHORITY_BOUNDARY },
|
|
283
|
+
authorizationGranted: false,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const event = readBaselineEvent(controllerRoot, delivery);
|
|
288
|
+
const current = stableWorkspaceSnapshot(targetRoot);
|
|
289
|
+
const refreshed = readTeamProjectStatusV4({ workspaceRoot: controllerRoot });
|
|
290
|
+
if (refreshed.projectTruthHash !== status.projectTruthHash) {
|
|
291
|
+
throw new Error("Project truth changed while completion continuity was being inspected.");
|
|
292
|
+
}
|
|
293
|
+
const drifted = current.sourceFingerprint !== event.targetSnapshot.sourceFingerprint;
|
|
294
|
+
return {
|
|
295
|
+
schemaVersion: "OwlCodaRunKitProjectCompletionContinuityV1",
|
|
296
|
+
status: drifted ? "workspace_drift" : "terminal",
|
|
297
|
+
projectId: status.projectId,
|
|
298
|
+
projectTruthHash: status.projectTruthHash,
|
|
299
|
+
disposition: drifted
|
|
300
|
+
? "completed_project_with_unmodeled_workspace_drift"
|
|
301
|
+
: "completed_project_terminal",
|
|
302
|
+
targetWorkspaceRef: targetRoot,
|
|
303
|
+
baseline: {
|
|
304
|
+
kind: "imported_source_delivery",
|
|
305
|
+
deliveryId: delivery.deliveryId,
|
|
306
|
+
occurredAt: delivery.occurredAt,
|
|
307
|
+
targetHead: delivery.targetHead,
|
|
308
|
+
workspaceSnapshotFingerprint: delivery.workspaceSnapshotFingerprint,
|
|
309
|
+
truthRef: delivery.truthRef,
|
|
310
|
+
},
|
|
311
|
+
current: {
|
|
312
|
+
headCommit: current.headCommit,
|
|
313
|
+
workspaceSnapshotFingerprint: current.sourceFingerprint,
|
|
314
|
+
},
|
|
315
|
+
delta: drifted ? sourceDelta(targetRoot, event.targetSnapshot, current) : null,
|
|
316
|
+
nextAction: drifted
|
|
317
|
+
? "Review the unmodeled workspace drift and author a successor only after confirming its objective and scope."
|
|
318
|
+
: null,
|
|
319
|
+
readOnly: true,
|
|
320
|
+
writesPerformed: false,
|
|
321
|
+
authorityBoundary: { ...AUTHORITY_BOUNDARY },
|
|
322
|
+
authorizationGranted: false,
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function hasExplicitDeliveryTruth(status) {
|
|
327
|
+
return status.externalGates.length > 0
|
|
328
|
+
|| status.deliveryLifecycle.stages.some(stage => stage.truthRef !== null);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function continuityDominantGap(continuity) {
|
|
332
|
+
if (continuity.disposition === "completed_project_with_unmodeled_workspace_drift") {
|
|
333
|
+
return {
|
|
334
|
+
kind: "unmodeled_workspace_drift",
|
|
335
|
+
id: "workspace-drift",
|
|
336
|
+
title: "Unmodeled workspace drift",
|
|
337
|
+
workItemId: null,
|
|
338
|
+
agentId: null,
|
|
339
|
+
reason: "The completed Project Driver ledger is older than the bound source workspace.",
|
|
340
|
+
truthRefs: [continuity.baseline.truthRef],
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
if (continuity.disposition === "completed_project_source_baseline_missing") {
|
|
344
|
+
return {
|
|
345
|
+
kind: "source_baseline",
|
|
346
|
+
id: "last-modeled-source",
|
|
347
|
+
title: "Source baseline missing",
|
|
348
|
+
workItemId: null,
|
|
349
|
+
agentId: null,
|
|
350
|
+
reason: "The completed Project Driver ledger has no explicit source snapshot baseline.",
|
|
351
|
+
truthRefs: ["project/definition.json"],
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
if (continuity.disposition === "completed_project_source_unverifiable") {
|
|
355
|
+
return {
|
|
356
|
+
kind: "source_baseline",
|
|
357
|
+
id: "last-modeled-source",
|
|
358
|
+
title: "Source baseline unverifiable",
|
|
359
|
+
workItemId: null,
|
|
360
|
+
agentId: null,
|
|
361
|
+
reason: "The modeled source workspace cannot be verified as an exact Git worktree root.",
|
|
362
|
+
truthRefs: ["project/definition.json"],
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
return {
|
|
366
|
+
kind: "none",
|
|
367
|
+
id: null,
|
|
368
|
+
title: null,
|
|
369
|
+
workItemId: null,
|
|
370
|
+
agentId: null,
|
|
371
|
+
reason: null,
|
|
372
|
+
truthRefs: continuity.baseline === null
|
|
373
|
+
? ["project/definition.json"]
|
|
374
|
+
: [continuity.baseline.truthRef],
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
export function readTeamProjectStatusV5({ workspaceRoot }) {
|
|
379
|
+
const controllerRoot = realpathSync(path.resolve(workspaceRoot));
|
|
380
|
+
const status = readTeamProjectStatusV4({ workspaceRoot: controllerRoot });
|
|
381
|
+
const completionContinuity = continuityProjection({ controllerRoot, status });
|
|
382
|
+
const explicitDeliveryTruth = hasExplicitDeliveryTruth(status);
|
|
383
|
+
const shouldProjectContinuity = status.overall === "completed" && (
|
|
384
|
+
completionContinuity.status === "workspace_drift"
|
|
385
|
+
|| !explicitDeliveryTruth
|
|
386
|
+
);
|
|
387
|
+
if (!shouldProjectContinuity) {
|
|
388
|
+
return {
|
|
389
|
+
...status,
|
|
390
|
+
schemaVersion: "OwlCodaRunKitTeamProjectStatusV5",
|
|
391
|
+
completionContinuity,
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
const dominantGap = continuityDominantGap(completionContinuity);
|
|
395
|
+
const nextAction = completionContinuity.nextAction;
|
|
396
|
+
return {
|
|
397
|
+
...status,
|
|
398
|
+
schemaVersion: "OwlCodaRunKitTeamProjectStatusV5",
|
|
399
|
+
completionContinuity,
|
|
400
|
+
headline: dominantGap.kind === "none"
|
|
401
|
+
? "Completed project is terminal against its bound source baseline; authorizationGranted=false."
|
|
402
|
+
: `Dominant gap: ${dominantGap.kind}; blocker: ${dominantGap.reason} next: ${nextAction}`,
|
|
403
|
+
dominantGap,
|
|
404
|
+
nextAction,
|
|
405
|
+
nextActorId: null,
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function pathPrefix(rule) {
|
|
410
|
+
return rule.endsWith("/**") ? rule.slice(0, -3) : null;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function pathCovered(rule, candidate) {
|
|
414
|
+
if (rule === candidate) return true;
|
|
415
|
+
const prefix = pathPrefix(rule);
|
|
416
|
+
return prefix !== null && (candidate === prefix || candidate.startsWith(`${prefix}/`));
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function scaffoldLanes(status, changedPaths) {
|
|
420
|
+
const matches = new Map(status.workItems.map(item => [item.workItemId, []]));
|
|
421
|
+
const unmatched = [];
|
|
422
|
+
const conflicts = [];
|
|
423
|
+
for (const changedPath of changedPaths) {
|
|
424
|
+
const owners = status.workItems.filter(item => (
|
|
425
|
+
item.ownedPaths.some(rule => pathCovered(rule, changedPath))
|
|
426
|
+
));
|
|
427
|
+
if (owners.length === 0) unmatched.push(changedPath);
|
|
428
|
+
for (const owner of owners) matches.get(owner.workItemId).push(changedPath);
|
|
429
|
+
if (owners.length > 1) {
|
|
430
|
+
conflicts.push({
|
|
431
|
+
path: changedPath,
|
|
432
|
+
historicalWorkItemIds: owners.map(item => item.workItemId).sort(compareCodeUnits),
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
const possibleLanes = status.workItems
|
|
437
|
+
.filter(item => matches.get(item.workItemId).length > 0)
|
|
438
|
+
.map(item => ({
|
|
439
|
+
laneId: `historical-scope:${item.workItemId}`,
|
|
440
|
+
basis: "historical_owned_paths_only",
|
|
441
|
+
historicalWorkItemId: item.workItemId,
|
|
442
|
+
suggestedOwnedPaths: uniqueSorted(matches.get(item.workItemId)),
|
|
443
|
+
requiresHumanConfirmation: true,
|
|
444
|
+
}));
|
|
445
|
+
if (unmatched.length > 0) {
|
|
446
|
+
possibleLanes.push({
|
|
447
|
+
laneId: "unassigned-paths",
|
|
448
|
+
basis: "unmatched_paths_only",
|
|
449
|
+
historicalWorkItemId: null,
|
|
450
|
+
suggestedOwnedPaths: uniqueSorted(unmatched),
|
|
451
|
+
requiresHumanConfirmation: true,
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
return { possibleLanes, pathConflicts: conflicts };
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
export function buildSuccessorScaffoldV1({ workspaceRoot, fromWorkspaceRoot }) {
|
|
458
|
+
const controllerRoot = realpathSync(path.resolve(workspaceRoot));
|
|
459
|
+
const status = readTeamProjectStatusV4({ workspaceRoot: controllerRoot });
|
|
460
|
+
if (status.overall !== "completed") {
|
|
461
|
+
throw new Error("Successor scaffold requires a completed Project Driver ledger.");
|
|
462
|
+
}
|
|
463
|
+
const targetRoot = canonicalGitRoot(fromWorkspaceRoot);
|
|
464
|
+
const completionContinuity = continuityProjection({
|
|
465
|
+
controllerRoot,
|
|
466
|
+
status,
|
|
467
|
+
targetWorkspaceRoot: targetRoot,
|
|
468
|
+
});
|
|
469
|
+
const changedPaths = completionContinuity.delta?.changedPaths ?? [];
|
|
470
|
+
const { possibleLanes, pathConflicts } = scaffoldLanes(status, changedPaths);
|
|
471
|
+
const drifted = completionContinuity.status === "workspace_drift";
|
|
472
|
+
const baselineMissing = completionContinuity.status === "baseline_missing";
|
|
473
|
+
return {
|
|
474
|
+
schemaVersion: "OwlCodaRunKitSuccessorScaffoldV1",
|
|
475
|
+
status: drifted
|
|
476
|
+
? "successor_review_required"
|
|
477
|
+
: baselineMissing
|
|
478
|
+
? "successor_baseline_missing"
|
|
479
|
+
: completionContinuity.status === "terminal"
|
|
480
|
+
? "successor_not_required"
|
|
481
|
+
: "successor_source_unverifiable",
|
|
482
|
+
exitCode: 0,
|
|
483
|
+
project: {
|
|
484
|
+
projectId: status.projectId,
|
|
485
|
+
projectTruthHash: status.projectTruthHash,
|
|
486
|
+
overall: status.overall,
|
|
487
|
+
},
|
|
488
|
+
completionContinuity,
|
|
489
|
+
unmodeledCandidates: drifted
|
|
490
|
+
? [{
|
|
491
|
+
candidateId: "workspace-drift",
|
|
492
|
+
kind: "workspace_source_delta",
|
|
493
|
+
targetWorkspaceRef: targetRoot,
|
|
494
|
+
baselineFingerprint: completionContinuity.baseline.workspaceSnapshotFingerprint,
|
|
495
|
+
currentFingerprint: completionContinuity.current.workspaceSnapshotFingerprint,
|
|
496
|
+
changedPaths,
|
|
497
|
+
changedPathCount: completionContinuity.delta.changedPathCount,
|
|
498
|
+
changedPathsTruncated: completionContinuity.delta.changedPathsTruncated,
|
|
499
|
+
pathEvidenceComplete: completionContinuity.delta.pathEvidenceComplete,
|
|
500
|
+
}]
|
|
501
|
+
: [],
|
|
502
|
+
pathConflicts,
|
|
503
|
+
possibleLanes,
|
|
504
|
+
draft: {
|
|
505
|
+
projectId: null,
|
|
506
|
+
objective: null,
|
|
507
|
+
workItems: possibleLanes.map(lane => ({
|
|
508
|
+
draftLaneId: lane.laneId,
|
|
509
|
+
title: null,
|
|
510
|
+
ownedPaths: [...lane.suggestedOwnedPaths],
|
|
511
|
+
ownerAgentId: null,
|
|
512
|
+
})),
|
|
513
|
+
requiresHumanConfirmation: [
|
|
514
|
+
"project_id",
|
|
515
|
+
"objective",
|
|
516
|
+
"work_item_boundaries",
|
|
517
|
+
"owners",
|
|
518
|
+
"acceptance",
|
|
519
|
+
],
|
|
520
|
+
},
|
|
521
|
+
nextAction: completionContinuity.nextAction,
|
|
522
|
+
persisted: false,
|
|
523
|
+
readOnly: true,
|
|
524
|
+
writesPerformed: false,
|
|
525
|
+
authorityBoundary: { ...AUTHORITY_BOUNDARY },
|
|
526
|
+
authorizationGranted: false,
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
export function formatSuccessorScaffoldHumanV1(scaffold) {
|
|
531
|
+
const continuity = scaffold.completionContinuity;
|
|
532
|
+
const lines = [
|
|
533
|
+
`Successor review: ${scaffold.status}`,
|
|
534
|
+
`Project: ${scaffold.project.projectId} (${scaffold.project.overall})`,
|
|
535
|
+
`Continuity: ${continuity.disposition}`,
|
|
536
|
+
`Source: ${continuity.targetWorkspaceRef ?? "not bound"}`,
|
|
537
|
+
`Unmodeled paths: ${scaffold.unmodeledCandidates[0]?.changedPathCount ?? 0}`,
|
|
538
|
+
`Possible lanes: ${scaffold.possibleLanes.length}; conflicts: ${scaffold.pathConflicts.length}`,
|
|
539
|
+
`Next: ${scaffold.nextAction ?? "stop; no successor is currently indicated"}`,
|
|
540
|
+
"Read-only: yes; project/event writes: 0; Agent dispatch: false.",
|
|
541
|
+
"Authorization: Git/release/deployment/production/business remain false.",
|
|
542
|
+
"",
|
|
543
|
+
];
|
|
544
|
+
return lines.join("\n");
|
|
545
|
+
}
|
|
@@ -47,10 +47,10 @@ const POLICY = Object.freeze({
|
|
|
47
47
|
],
|
|
48
48
|
rollbackRelease: {
|
|
49
49
|
packageName: PACKAGE_NAME,
|
|
50
|
-
version: "0.
|
|
51
|
-
shasum: "
|
|
52
|
-
integrity: "sha512-
|
|
53
|
-
tarballUrl: "https://registry.npmjs.org/owlrunkit/-/owlrunkit-0.
|
|
50
|
+
version: "0.22.1",
|
|
51
|
+
shasum: "675b417134825e876e6e2b7c2e8d2aea4045e59f",
|
|
52
|
+
integrity: "sha512-Vp+o6u00Di18fuPFcbEzdBpRd79e8P4yZq77RF2wuJG5ITjUGf/pAzOb5sOOVzj8P/MXcnhKcnRQKT70kUyZuA==",
|
|
53
|
+
tarballUrl: "https://registry.npmjs.org/owlrunkit/-/owlrunkit-0.22.1.tgz",
|
|
54
54
|
},
|
|
55
55
|
registryEvidenceMustBeExternal: true,
|
|
56
56
|
registryEvidenceSchemaVersion: "OwlCodaRunKitRegistryReleaseEvidenceV1",
|
|
@@ -195,7 +195,6 @@ import {
|
|
|
195
195
|
openTeamProjectDecisionV1,
|
|
196
196
|
readTeamProjectStatusV2,
|
|
197
197
|
readTeamProjectStatusV3,
|
|
198
|
-
readTeamProjectStatusV4,
|
|
199
198
|
reconcileExternalGateV1,
|
|
200
199
|
recordDeliveryLifecycleV1,
|
|
201
200
|
rejectAndReturnTeamProjectV1,
|
|
@@ -205,6 +204,11 @@ import {
|
|
|
205
204
|
setWorkItemEvidenceRequirementV1,
|
|
206
205
|
transferTeamProjectV1,
|
|
207
206
|
} from "./team-project.mjs";
|
|
207
|
+
import {
|
|
208
|
+
buildSuccessorScaffoldV1,
|
|
209
|
+
formatSuccessorScaffoldHumanV1,
|
|
210
|
+
readTeamProjectStatusV5,
|
|
211
|
+
} from "./project-completion-continuity.mjs";
|
|
208
212
|
import {
|
|
209
213
|
formatTeamProjectSuccessorHumanV1,
|
|
210
214
|
successorTeamProjectV1,
|
|
@@ -258,7 +262,7 @@ Fleet and status
|
|
|
258
262
|
npx --no-install owlrunkit fleet discover; npx --no-install owlrunkit status --workspace .
|
|
259
263
|
npx --no-install owlrunkit hook recovery --workspace . --json
|
|
260
264
|
Agent team projects
|
|
261
|
-
npx --no-install owlrunkit project <init|assign|checkpoint|handoff|reject-and-return|reopen-upstream|transfer|work-item revise-scope|work-item require-evidence|reconcile|delivery record|decision|verification|capture|target-snapshot|import-delivery|status|takeover|successor> [options]
|
|
265
|
+
npx --no-install owlrunkit project <init|assign|checkpoint|handoff|reject-and-return|reopen-upstream|transfer|work-item revise-scope|work-item require-evidence|reconcile|delivery record|decision|verification|capture|target-snapshot|import-delivery|status|takeover|successor|successor scaffold> [options]
|
|
262
266
|
npx --no-install owlrunkit team <recommend|status|packet validate> [options]
|
|
263
267
|
Durable project truth plus read-only Team recommendation and packet validation. No Agent dispatch.
|
|
264
268
|
Core successor: npx --no-install owlrunkit core-successor <plan|apply|resume> --workspace . [...]
|
|
@@ -741,6 +745,8 @@ action without granting any authority.
|
|
|
741
745
|
npx --no-install owlrunkit project takeover --workspace <directory> --agent <id> [--json]
|
|
742
746
|
npx --no-install owlrunkit project successor --workspace <directory> --transition-id <id> --at <ISO-UTC>
|
|
743
747
|
--definition <project.json> --reason <text> [--json]
|
|
748
|
+
npx --no-install owlrunkit project successor scaffold --workspace <directory>
|
|
749
|
+
--from-workspace <git-worktree> --dry-run [--json]
|
|
744
750
|
|
|
745
751
|
Project status is derived from durable work items, Agent assignments,
|
|
746
752
|
dependencies, decisions, checkpoints, integration gates, and evidence refs.
|
|
@@ -909,6 +915,16 @@ Builds a read-only blank-session recovery packet for one Agent.
|
|
|
909
915
|
|
|
910
916
|
Archives one derived-completed active project and installs one fresh active
|
|
911
917
|
project with no inherited events. The receipt grants no repository or release authority.
|
|
918
|
+
`,
|
|
919
|
+
"project successor scaffold": `Usage:
|
|
920
|
+
npx --no-install owlrunkit project successor scaffold --workspace <directory>
|
|
921
|
+
--from-workspace <git-worktree> --dry-run [--json]
|
|
922
|
+
|
|
923
|
+
Reads one completed Project Driver ledger, its latest explicitly imported source
|
|
924
|
+
delivery baseline, and the exact current Git worktree. It reports terminal,
|
|
925
|
+
baseline-missing, or unmodeled-drift truth plus review-only path lanes. It does
|
|
926
|
+
not create a project, append an event, infer an objective, dispatch an Agent, or
|
|
927
|
+
grant Git, release, deployment, production, or business authority.
|
|
912
928
|
`,
|
|
913
929
|
});
|
|
914
930
|
|
|
@@ -2169,7 +2185,7 @@ function inspect(options) {
|
|
|
2169
2185
|
const onboarding = onboardingProjection(root, recovery);
|
|
2170
2186
|
const projectDefinitionPath = runtimePath(root, "project", "definition.json");
|
|
2171
2187
|
const projectDriver = pathEntryExists(projectDefinitionPath)
|
|
2172
|
-
?
|
|
2188
|
+
? readTeamProjectStatusV5({ workspaceRoot: root })
|
|
2173
2189
|
: null;
|
|
2174
2190
|
const localMaintenance = options.compact === true
|
|
2175
2191
|
? inspectLocalMaintenanceV1({
|
|
@@ -2487,6 +2503,11 @@ export async function runCli(argv = process.argv.slice(2), runtimeHooks = {}) {
|
|
|
2487
2503
|
action = "delivery record";
|
|
2488
2504
|
nestedOptions = nestedOptions.slice(1);
|
|
2489
2505
|
}
|
|
2506
|
+
if (command === "project" && action === "successor"
|
|
2507
|
+
&& nestedOptions[0] === "scaffold") {
|
|
2508
|
+
action = "successor scaffold";
|
|
2509
|
+
nestedOptions = nestedOptions.slice(1);
|
|
2510
|
+
}
|
|
2490
2511
|
if (command === "team" && action === "packet"
|
|
2491
2512
|
&& nestedOptions[0] === "validate") {
|
|
2492
2513
|
action = "packet validate";
|
|
@@ -2596,6 +2617,8 @@ export async function runCli(argv = process.argv.slice(2), runtimeHooks = {}) {
|
|
|
2596
2617
|
? ["json"]
|
|
2597
2618
|
: command === "project" && new Set(["init", "assign", "handoff", "reject-and-return", "reopen-upstream", "transfer", "checkpoint", "integrate", "capture", "reconcile", "delivery record", "status", "takeover", "successor"]).has(action)
|
|
2598
2619
|
? ["json"]
|
|
2620
|
+
: command === "project" && action === "successor scaffold"
|
|
2621
|
+
? ["json", "dry-run"]
|
|
2599
2622
|
: command === "project" && new Set(["target-snapshot", "import-delivery"]).has(action)
|
|
2600
2623
|
? ["json", "dry-run", "apply"]
|
|
2601
2624
|
: command === "doctor" || command === "adopt" || command === "status"
|
|
@@ -3722,6 +3745,7 @@ export async function runCli(argv = process.argv.slice(2), runtimeHooks = {}) {
|
|
|
3722
3745
|
status: ["workspace", "json"],
|
|
3723
3746
|
takeover: ["workspace", "agent", "json"],
|
|
3724
3747
|
successor: ["workspace", "transition-id", "at", "definition", "reason", "json"],
|
|
3748
|
+
"successor scaffold": ["workspace", "from-workspace", "dry-run", "json"],
|
|
3725
3749
|
};
|
|
3726
3750
|
if (Object.hasOwn(allowedProjectOptions, action)) {
|
|
3727
3751
|
assertOnlyOptions(options, allowedProjectOptions[action]);
|
|
@@ -4361,7 +4385,7 @@ export async function runCli(argv = process.argv.slice(2), runtimeHooks = {}) {
|
|
|
4361
4385
|
});
|
|
4362
4386
|
}
|
|
4363
4387
|
if (action === "status") {
|
|
4364
|
-
const status =
|
|
4388
|
+
const status = readTeamProjectStatusV5({ workspaceRoot: root });
|
|
4365
4389
|
return options.json === true
|
|
4366
4390
|
? status
|
|
4367
4391
|
: withHumanOutput(status, formatTeamProjectStatusHumanV2(status));
|
|
@@ -4388,7 +4412,19 @@ export async function runCli(argv = process.argv.slice(2), runtimeHooks = {}) {
|
|
|
4388
4412
|
? successor
|
|
4389
4413
|
: withHumanOutput(successor, formatTeamProjectSuccessorHumanV1(successor));
|
|
4390
4414
|
}
|
|
4391
|
-
|
|
4415
|
+
if (action === "successor scaffold") {
|
|
4416
|
+
if (options["dry-run"] !== true) {
|
|
4417
|
+
throw new Error("project successor scaffold requires --dry-run.");
|
|
4418
|
+
}
|
|
4419
|
+
const scaffold = buildSuccessorScaffoldV1({
|
|
4420
|
+
workspaceRoot: root,
|
|
4421
|
+
fromWorkspaceRoot: requireOption(options, "from-workspace"),
|
|
4422
|
+
});
|
|
4423
|
+
return options.json === true
|
|
4424
|
+
? scaffold
|
|
4425
|
+
: withHumanOutput(scaffold, formatSuccessorScaffoldHumanV1(scaffold));
|
|
4426
|
+
}
|
|
4427
|
+
throw new Error("Usage: npx --no-install owlrunkit project <init|assign|checkpoint|handoff|reject-and-return|reopen-upstream|transfer|work-item revise-scope|reconcile|delivery record|decision|verification|integrate|capture|target-snapshot|import-delivery|status|takeover|successor|successor scaffold> [options]");
|
|
4392
4428
|
}
|
|
4393
4429
|
if (command === "profiles") {
|
|
4394
4430
|
const root = requireOption(options, "workspace");
|
|
@@ -115,7 +115,7 @@
|
|
|
115
115
|
"status": "blocked",
|
|
116
116
|
"expectedCore": {
|
|
117
117
|
"contractVersion": "0.2",
|
|
118
|
-
"coreVersion": "0.
|
|
118
|
+
"coreVersion": "0.23.0",
|
|
119
119
|
"coreManifestSha256": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
|
120
120
|
"coreSourceRef": "artifact:sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
|
121
121
|
},
|