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.
@@ -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.21.0",
51
- shasum: "f9ecfa41ad2ba3140258234b49d98798675651f2",
52
- integrity: "sha512-hopLyvN5vp0y3xu0H0jQbMI6DxaDSWcGYcn7tAbCa0m2OqW2G8n5kJvnr217jqPYlArQwJ2TDPRLNmp4vPEhVQ==",
53
- tarballUrl: "https://registry.npmjs.org/owlrunkit/-/owlrunkit-0.21.0.tgz",
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
- ? readTeamProjectStatusV3({ workspaceRoot: root })
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 = readTeamProjectStatusV4({ workspaceRoot: root });
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
- 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> [options]");
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.22.1",
118
+ "coreVersion": "0.23.0",
119
119
  "coreManifestSha256": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
120
120
  "coreSourceRef": "artifact:sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
121
121
  },