owlrunkit 0.18.0 → 0.18.2
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 +41 -0
- package/README.md +61 -17
- package/docs/architecture/OWLCODA_RUNKIT_USAGE_MODE_SELECTION_PRODUCT_DECISION_20260811.md +166 -0
- package/integrations/codex/skills/owlcoda-runkit/SKILL.md +41 -16
- package/integrations/codex/skills/owlcoda-runkit/assets/templates/config.json +3 -3
- package/package.json +1 -1
- package/packages/attest/distribution-manifest-v1.json +11 -1
- package/packages/attest/src/quick.mjs +11 -1
- package/registry-release-policy-v1.json +5 -5
- package/sbom.spdx.json +5 -5
- package/scripts/runkit-contract/assurance-router.mjs +31 -21
- package/scripts/runkit-contract/core-contract.mjs +5 -1
- package/scripts/runkit-contract/inspect-presentation.mjs +38 -5
- package/scripts/runkit-contract/install-codex-skill.mjs +60 -1
- package/scripts/runkit-contract/onboarding-doctor.mjs +65 -19
- package/scripts/runkit-contract/profile-onboarding.mjs +601 -22
- package/scripts/runkit-contract/project-cli-resolver.mjs +6 -0
- package/scripts/runkit-contract/quick-attest.mjs +1 -0
- package/scripts/runkit-contract/quick-metrics.mjs +43 -5
- package/scripts/runkit-contract/quick-receipt.mjs +43 -5
- package/scripts/runkit-contract/quick-verify.mjs +20 -4
- package/scripts/runkit-contract/runkit-cli.mjs +278 -24
- package/scripts/runkit-contract/schemas/profile-reconcile-plan-v1.schema.json +107 -0
- package/scripts/runkit-contract/schemas/profile-reconcile-receipt-v1.schema.json +60 -0
- package/scripts/runkit-contract/schemas/usage-mode-recommendation-v1.schema.json +136 -0
- package/scripts/runkit-contract/templates/assurance-request.json +6 -0
- package/scripts/runkit-contract/usage-mode.mjs +227 -0
|
@@ -6,9 +6,40 @@ import {
|
|
|
6
6
|
} from "node:fs";
|
|
7
7
|
import path from "node:path";
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
decodeUtf8Strict,
|
|
11
|
+
parseJsonStrict,
|
|
12
|
+
readFileBytesBounded,
|
|
13
|
+
} from "../../packages/attest/src/formal.mjs";
|
|
14
|
+
import { quickSnapshotFingerprintValid } from "../../packages/attest/src/quick-receipt-contract.mjs";
|
|
15
|
+
import {
|
|
16
|
+
attestQuickReceipt,
|
|
17
|
+
validQuickReceiptShape,
|
|
18
|
+
} from "./quick-attest.mjs";
|
|
10
19
|
import { quickReceiptRoot } from "./quick-receipt.mjs";
|
|
11
20
|
|
|
21
|
+
const INVALID_ATTESTATION_ISSUES = new Set([
|
|
22
|
+
"attestation_material_missing",
|
|
23
|
+
"receipt_material_hash_mismatch",
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
function readHistoricalReceipt(receiptPath) {
|
|
27
|
+
const { bytes } = readFileBytesBounded(receiptPath);
|
|
28
|
+
const receipt = parseJsonStrict(decodeUtf8Strict(bytes));
|
|
29
|
+
if (!validQuickReceiptShape(receipt)) throw new Error("quick_receipt_schema_invalid");
|
|
30
|
+
const snapshotsValid = quickSnapshotFingerprintValid(receipt.workspaceBefore)
|
|
31
|
+
&& quickSnapshotFingerprintValid(receipt.workspaceAfter);
|
|
32
|
+
const sourceChanged = receipt.workspaceBefore.sourceFingerprint
|
|
33
|
+
!== receipt.workspaceAfter.sourceFingerprint;
|
|
34
|
+
const mutationBindingValid = receipt.mutationDecision === "source_unchanged"
|
|
35
|
+
? !sourceChanged && !receipt.issueCodes.includes("source_mutated_during_verification")
|
|
36
|
+
: sourceChanged && receipt.issueCodes.includes("source_mutated_during_verification");
|
|
37
|
+
if (!snapshotsValid || !mutationBindingValid) {
|
|
38
|
+
throw new Error("quick_receipt_source_binding_invalid");
|
|
39
|
+
}
|
|
40
|
+
return receipt;
|
|
41
|
+
}
|
|
42
|
+
|
|
12
43
|
function trustedReceiptRoot(workspaceRoot) {
|
|
13
44
|
const workspace = realpathSync(workspaceRoot);
|
|
14
45
|
const root = quickReceiptRoot(workspace);
|
|
@@ -65,10 +96,17 @@ export function readLocalQuickMetrics(workspaceRoot) {
|
|
|
65
96
|
};
|
|
66
97
|
for (const receiptPath of receiptPaths) {
|
|
67
98
|
try {
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
99
|
+
const receipt = readHistoricalReceipt(receiptPath);
|
|
100
|
+
const attestation = attestQuickReceipt({ receiptPath, workspaceRoot });
|
|
101
|
+
if (attestation.issueCodes.some((issue) => INVALID_ATTESTATION_ISSUES.has(issue))) {
|
|
102
|
+
totals.invalid += 1;
|
|
103
|
+
} else if (receipt.mutationDecision === "invalidated_by_command_source_mutation") {
|
|
104
|
+
totals.sourceMutated += 1;
|
|
105
|
+
} else if (receipt.exitResult.exitCode === 0 && receipt.exitResult.signal === null) {
|
|
106
|
+
totals.passed += 1;
|
|
107
|
+
} else {
|
|
108
|
+
totals.failed += 1;
|
|
109
|
+
}
|
|
72
110
|
} catch {
|
|
73
111
|
totals.invalid += 1;
|
|
74
112
|
}
|
|
@@ -16,6 +16,8 @@ import path from "node:path";
|
|
|
16
16
|
import { sha256Bytes } from "./quick-canonical.mjs";
|
|
17
17
|
|
|
18
18
|
const QUICK_ROOT = ".owlcoda/runkit/quick";
|
|
19
|
+
const OUTPUT_SUMMARY_BYTES = 16 * 1024;
|
|
20
|
+
const OUTPUT_SUMMARY_LINES = 3;
|
|
19
21
|
|
|
20
22
|
function withinRoot(root, candidate) {
|
|
21
23
|
const remainder = path.relative(root, candidate);
|
|
@@ -87,12 +89,40 @@ export function closeQuickOutputFiles(store) {
|
|
|
87
89
|
closeSync(store.stderrFd);
|
|
88
90
|
}
|
|
89
91
|
|
|
92
|
+
function cleanOutputLine(line) {
|
|
93
|
+
const withoutAnsi = line.replace(/\u001b\[[0-?]*[ -/]*[@-~]/gu, "");
|
|
94
|
+
return [...withoutAnsi]
|
|
95
|
+
.filter((character) => character === "\t" || character.codePointAt(0) >= 32)
|
|
96
|
+
.join("")
|
|
97
|
+
.trim();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function summarizeOutputBytes(bytes) {
|
|
101
|
+
const start = Math.max(0, bytes.byteLength - OUTPUT_SUMMARY_BYTES);
|
|
102
|
+
let text = bytes.subarray(start).toString("utf8");
|
|
103
|
+
if (start > 0) {
|
|
104
|
+
const firstLineBreak = text.indexOf("\n");
|
|
105
|
+
if (firstLineBreak >= 0) text = text.slice(firstLineBreak + 1);
|
|
106
|
+
}
|
|
107
|
+
const lines = text
|
|
108
|
+
.split(/\r?\n/u)
|
|
109
|
+
.map(cleanOutputLine)
|
|
110
|
+
.filter(Boolean);
|
|
111
|
+
return {
|
|
112
|
+
lines: lines.slice(-OUTPUT_SUMMARY_LINES),
|
|
113
|
+
truncated: start > 0 || lines.length > OUTPUT_SUMMARY_LINES,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
90
117
|
function outputArtifact(workspaceRoot, filePath) {
|
|
91
118
|
const bytes = readFileSync(filePath);
|
|
92
119
|
return {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
120
|
+
artifact: {
|
|
121
|
+
path: path.relative(realpathSync(workspaceRoot), filePath).split(path.sep).join("/"),
|
|
122
|
+
sha256: sha256Bytes(bytes),
|
|
123
|
+
sizeBytes: bytes.byteLength,
|
|
124
|
+
},
|
|
125
|
+
summary: summarizeOutputBytes(bytes),
|
|
96
126
|
};
|
|
97
127
|
}
|
|
98
128
|
|
|
@@ -119,11 +149,13 @@ function validateQuickReceiptStore(workspaceRoot, store) {
|
|
|
119
149
|
|
|
120
150
|
export function persistQuickReceipt({ workspaceRoot, store, receipt }) {
|
|
121
151
|
validateQuickReceiptStore(workspaceRoot, store);
|
|
152
|
+
const stdout = outputArtifact(workspaceRoot, store.stdoutPath);
|
|
153
|
+
const stderr = outputArtifact(workspaceRoot, store.stderrPath);
|
|
122
154
|
const complete = {
|
|
123
155
|
...receipt,
|
|
124
156
|
outputArtifacts: {
|
|
125
|
-
stdout:
|
|
126
|
-
stderr:
|
|
157
|
+
stdout: stdout.artifact,
|
|
158
|
+
stderr: stderr.artifact,
|
|
127
159
|
},
|
|
128
160
|
};
|
|
129
161
|
atomicWriteJson(store.receiptPath, complete);
|
|
@@ -131,6 +163,12 @@ export function persistQuickReceipt({ workspaceRoot, store, receipt }) {
|
|
|
131
163
|
receipt: complete,
|
|
132
164
|
receiptPath: store.receiptPath,
|
|
133
165
|
receiptSha256: sha256Bytes(readFileSync(store.receiptPath)),
|
|
166
|
+
outputSummary: {
|
|
167
|
+
stdoutTail: stdout.summary.lines,
|
|
168
|
+
stderrTail: stderr.summary.lines,
|
|
169
|
+
stdoutTruncated: stdout.summary.truncated,
|
|
170
|
+
stderrTruncated: stderr.summary.truncated,
|
|
171
|
+
},
|
|
134
172
|
};
|
|
135
173
|
}
|
|
136
174
|
|
|
@@ -9,6 +9,14 @@ import {
|
|
|
9
9
|
} from "./quick-receipt.mjs";
|
|
10
10
|
import { captureWorkspaceSnapshot } from "./quick-workspace-snapshot.mjs";
|
|
11
11
|
|
|
12
|
+
function shellQuote(value) {
|
|
13
|
+
return `'${String(value).replaceAll("'", `'"'"'`)}'`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function quickAttestCommand(workspaceRoot, receiptPath) {
|
|
17
|
+
return `owlrunkit quick-attest --workspace ${shellQuote(workspaceRoot)} --receipt ${shellQuote(receiptPath)}`;
|
|
18
|
+
}
|
|
19
|
+
|
|
12
20
|
function runExactCommand({ executable, argv, cwd, stdoutFd, stderrFd }) {
|
|
13
21
|
return new Promise((resolve) => {
|
|
14
22
|
let settled = false;
|
|
@@ -152,7 +160,10 @@ export async function runQuickVerification({ workspaceRoot, commandArgv }) {
|
|
|
152
160
|
};
|
|
153
161
|
}
|
|
154
162
|
|
|
163
|
+
const { outputSummary } = persisted;
|
|
164
|
+
|
|
155
165
|
if (sourceMutated) {
|
|
166
|
+
const attestCommand = quickAttestCommand(root, persisted.receiptPath);
|
|
156
167
|
return {
|
|
157
168
|
status: "quick_verification_source_mutated",
|
|
158
169
|
exitCode: 2,
|
|
@@ -162,13 +173,15 @@ export async function runQuickVerification({ workspaceRoot, commandArgv }) {
|
|
|
162
173
|
commandExitCode: execution.exitCode,
|
|
163
174
|
mutationDecision: "invalidated_by_command_source_mutation",
|
|
164
175
|
issueCodes,
|
|
165
|
-
|
|
176
|
+
outputSummary,
|
|
177
|
+
attestCommand,
|
|
166
178
|
issues: [],
|
|
167
179
|
nextAllowedAction: "restore_source_or_run_a_new_quick_verification",
|
|
168
180
|
authorizationGranted: false,
|
|
169
181
|
};
|
|
170
182
|
}
|
|
171
183
|
if (execution.launchError || execution.exitCode !== 0) {
|
|
184
|
+
const attestCommand = quickAttestCommand(root, persisted.receiptPath);
|
|
172
185
|
return {
|
|
173
186
|
status: "quick_verification_failed",
|
|
174
187
|
exitCode: 1,
|
|
@@ -178,12 +191,14 @@ export async function runQuickVerification({ workspaceRoot, commandArgv }) {
|
|
|
178
191
|
commandExitCode: execution.exitCode,
|
|
179
192
|
mutationDecision: "source_unchanged",
|
|
180
193
|
issueCodes,
|
|
181
|
-
|
|
194
|
+
outputSummary,
|
|
195
|
+
attestCommand,
|
|
182
196
|
issues: execution.launchError ? [execution.launchError] : [],
|
|
183
197
|
nextAllowedAction: "inspect_output_and_rerun",
|
|
184
198
|
authorizationGranted: false,
|
|
185
199
|
};
|
|
186
200
|
}
|
|
201
|
+
const attestCommand = quickAttestCommand(root, persisted.receiptPath);
|
|
187
202
|
return {
|
|
188
203
|
status: "quick_verification_passed",
|
|
189
204
|
exitCode: 0,
|
|
@@ -193,9 +208,10 @@ export async function runQuickVerification({ workspaceRoot, commandArgv }) {
|
|
|
193
208
|
commandExitCode: 0,
|
|
194
209
|
mutationDecision: "source_unchanged",
|
|
195
210
|
issueCodes,
|
|
196
|
-
|
|
211
|
+
outputSummary,
|
|
212
|
+
attestCommand,
|
|
197
213
|
issues: [],
|
|
198
|
-
nextAllowedAction:
|
|
214
|
+
nextAllowedAction: attestCommand,
|
|
199
215
|
authorizationGranted: false,
|
|
200
216
|
};
|
|
201
217
|
}
|
|
@@ -89,6 +89,7 @@ import {
|
|
|
89
89
|
applyDetectedProfilesV2,
|
|
90
90
|
detectProfiles,
|
|
91
91
|
detectProfilesV2,
|
|
92
|
+
reconcileProfilesV2,
|
|
92
93
|
resolveProfilesImpact,
|
|
93
94
|
validateProfiles,
|
|
94
95
|
} from "./profile-onboarding.mjs";
|
|
@@ -99,7 +100,14 @@ import {
|
|
|
99
100
|
replaceFleetRegistry,
|
|
100
101
|
rollbackFleetRegistry,
|
|
101
102
|
} from "./fleet-discovery.mjs";
|
|
102
|
-
import {
|
|
103
|
+
import {
|
|
104
|
+
RUNKIT_RISK_CATEGORIES,
|
|
105
|
+
routeRunKitAssuranceV1,
|
|
106
|
+
} from "./assurance-router.mjs";
|
|
107
|
+
import {
|
|
108
|
+
formatUsageModeHumanV1,
|
|
109
|
+
recommendUsageModeV1,
|
|
110
|
+
} from "./usage-mode.mjs";
|
|
103
111
|
import { deriveHumanStatusFromInspectV1 } from "./human-status.mjs";
|
|
104
112
|
import {
|
|
105
113
|
closeDeploymentExecuteChildRun,
|
|
@@ -163,20 +171,20 @@ Usage:
|
|
|
163
171
|
owlrunkit <command> [options]
|
|
164
172
|
|
|
165
173
|
Start here:
|
|
174
|
+
owlrunkit mode recommend --workspace .
|
|
166
175
|
owlrunkit doctor --workspace .
|
|
167
176
|
owlrunkit profiles detect --workspace . --dry-run
|
|
168
177
|
owlrunkit profiles detect --workspace . --apply
|
|
169
178
|
owlrunkit assurance route --workspace . --request <risk-facts.json>
|
|
170
179
|
owlrunkit adopt --workspace . --exact owlrunkit@${CORE_VERSION}
|
|
171
|
-
|
|
172
180
|
Quick Verification
|
|
173
181
|
owlrunkit quick-verify --workspace . -- <command> [args...]
|
|
174
182
|
Low-risk, source-bound reusable evidence without writer leases or acceptance.
|
|
175
183
|
|
|
176
184
|
Formal Delivery
|
|
177
185
|
owlrunkit formal <start|check|finish> --workspace . --run-id <id> [...]
|
|
178
|
-
Use for multi-writer,
|
|
179
|
-
|
|
186
|
+
Use for multi-writer, permissions, security, funds, irreversible change,
|
|
187
|
+
uncertain rollback, schema/data migration, or explicit Formal acceptance.
|
|
180
188
|
|
|
181
189
|
Frozen dirty source
|
|
182
190
|
owlrunkit candidate <freeze|verify|materialize> --workspace . [...]
|
|
@@ -204,6 +212,13 @@ Formal accepted does not grant Git or release authority; Quick cannot become For
|
|
|
204
212
|
`;
|
|
205
213
|
|
|
206
214
|
const COMMAND_HELP = Object.freeze({
|
|
215
|
+
adopt: `Usage:
|
|
216
|
+
owlrunkit adopt --workspace <directory> --exact owlrunkit@${CORE_VERSION}
|
|
217
|
+
[--registry <url>] [--timeout-ms <milliseconds>] [--json]
|
|
218
|
+
|
|
219
|
+
Verifies the exact registry release against the project-local install and writes
|
|
220
|
+
project-owned adoption evidence. It grants no Git or release authority.
|
|
221
|
+
`,
|
|
207
222
|
assurance: `Usage:
|
|
208
223
|
owlrunkit assurance route --workspace <directory> --request <risk-facts.json>
|
|
209
224
|
|
|
@@ -215,6 +230,45 @@ repository, release, deployment, or destructive authority.
|
|
|
215
230
|
|
|
216
231
|
Required:
|
|
217
232
|
--request <file> Structured assurance risk facts.
|
|
233
|
+
`,
|
|
234
|
+
mode: `Usage:
|
|
235
|
+
owlrunkit mode recommend --workspace <directory> [risk facts]
|
|
236
|
+
owlrunkit mode status --workspace <directory> [--json]
|
|
237
|
+
|
|
238
|
+
Selects continuity and assurance independently. Recommendation and status are
|
|
239
|
+
read-only and grant no Git, release, deployment, production, or business authority.
|
|
240
|
+
`,
|
|
241
|
+
"mode recommend": `Usage:
|
|
242
|
+
owlrunkit mode recommend --workspace <directory>
|
|
243
|
+
[--requested-mode auto|off|light|managed|formal]
|
|
244
|
+
[--needs-evidence] [--exact-command-count <count>] [--writer-count <count>]
|
|
245
|
+
[--cross-session] [--long-running] [--interruption-recovery]
|
|
246
|
+
[--may-mutate-project] [--external-side-effects]
|
|
247
|
+
[--irreversible] [--rollback-uncertain]
|
|
248
|
+
[--permission-sensitive] [--security-sensitive] [--funds-at-risk]
|
|
249
|
+
[--schema-or-data-migration] [--formal-acceptance]
|
|
250
|
+
[--risk-category <${RUNKIT_RISK_CATEGORIES.join("|")}>] [--json]
|
|
251
|
+
|
|
252
|
+
Returns one user mode plus the independent continuity and assurance selections.
|
|
253
|
+
Persisted mode preferences are not part of this release.
|
|
254
|
+
`,
|
|
255
|
+
"mode status": `Usage:
|
|
256
|
+
owlrunkit mode status --workspace <directory> [--json]
|
|
257
|
+
|
|
258
|
+
Reads the current mode preference source without creating project state.
|
|
259
|
+
`,
|
|
260
|
+
"quick-attest": `Usage:
|
|
261
|
+
owlrunkit quick-attest --workspace <directory> --receipt <receipt.json> [--json]
|
|
262
|
+
|
|
263
|
+
Attests one Quick Receipt against its bound materials and current workspace.
|
|
264
|
+
It never grants repository or business authority.
|
|
265
|
+
`,
|
|
266
|
+
"quick-verify": `Usage:
|
|
267
|
+
owlrunkit quick-verify --workspace <directory> [--json] -- <executable> [args...]
|
|
268
|
+
|
|
269
|
+
Runs one exact command, saves source-bound stdout/stderr and a Quick receipt,
|
|
270
|
+
and returns a bounded output summary. Supply the exact command after --.
|
|
271
|
+
Quick evidence is not Formal acceptance and grants no repository or business authority.
|
|
218
272
|
`,
|
|
219
273
|
fleet: `Usage:
|
|
220
274
|
owlrunkit fleet register-root --fleet-root <directory>
|
|
@@ -254,6 +308,7 @@ With no source option, discovers the complete fleet inside registered roots.
|
|
|
254
308
|
`,
|
|
255
309
|
profiles: `Usage:
|
|
256
310
|
owlrunkit profiles detect --workspace <directory> [--dry-run | --apply]
|
|
311
|
+
owlrunkit profiles reconcile --workspace <directory> (--dry-run | --apply)
|
|
257
312
|
owlrunkit profiles validate --workspace <directory>
|
|
258
313
|
owlrunkit profiles impact --workspace <directory> --changed <path>
|
|
259
314
|
`,
|
|
@@ -262,6 +317,12 @@ With no source option, discovers the complete fleet inside registered roots.
|
|
|
262
317
|
|
|
263
318
|
--dry-run reports deterministic candidates. --apply atomically adopts only a
|
|
264
319
|
high-confidence unambiguous profile.
|
|
320
|
+
`,
|
|
321
|
+
"profiles reconcile": `Usage:
|
|
322
|
+
owlrunkit profiles reconcile --workspace <directory> (--dry-run | --apply)
|
|
323
|
+
|
|
324
|
+
--dry-run previews deterministic additions and command updates without writing.
|
|
325
|
+
--apply atomically updates profiles.json and writes a hash-bound migration receipt.
|
|
265
326
|
`,
|
|
266
327
|
formal: `Usage:
|
|
267
328
|
owlrunkit formal start --workspace <directory> --run-id <id> [...]
|
|
@@ -386,6 +447,18 @@ action without granting any authority.
|
|
|
386
447
|
Project status is derived from durable work items, Agent assignments,
|
|
387
448
|
dependencies, decisions, checkpoints, integration gates, and evidence refs.
|
|
388
449
|
No self-reported percentage is accepted.
|
|
450
|
+
`,
|
|
451
|
+
"project checkpoint": `Usage:
|
|
452
|
+
owlrunkit project checkpoint --workspace <directory> --checkpoint-id <id> --at <ISO-UTC>
|
|
453
|
+
--assignment-id <current> --work-item <id>
|
|
454
|
+
--state <active|waiting_dependency|waiting_decision|verifying|ready_to_integrate|completed|failed>
|
|
455
|
+
--summary <text> [--completed-units <nonnegative-int>]
|
|
456
|
+
[--evidence <ref> ...] [--blocker <ref> ...] [--decision <id> ...]
|
|
457
|
+
[--next <text>] [--source-fingerprint <sha256>] [--json]
|
|
458
|
+
|
|
459
|
+
Measured WorkItems require --completed-units on every checkpoint. A completed
|
|
460
|
+
checkpoint must use the WorkItem's declared total. Example:
|
|
461
|
+
owlrunkit project checkpoint --workspace . --checkpoint-id checkpoint-x --at 2026-08-11T00:00:00.000Z --assignment-id assign-x --work-item x --state completed --summary 'Completed x.' --completed-units 5 --evidence receipt:quick
|
|
389
462
|
`,
|
|
390
463
|
"project status": `Usage:
|
|
391
464
|
owlrunkit project status --workspace <directory> [--json]
|
|
@@ -414,6 +487,25 @@ function withHumanOutput(result, humanOutput) {
|
|
|
414
487
|
return result;
|
|
415
488
|
}
|
|
416
489
|
|
|
490
|
+
function shellCommandToken(value) {
|
|
491
|
+
const text = String(value);
|
|
492
|
+
return /^[A-Za-z0-9_@%+=:,./-]+$/u.test(text)
|
|
493
|
+
? text
|
|
494
|
+
: `'${text.replaceAll("'", `'"'"'`)}'`;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
function correctedCheckpointCommand(argv, completedUnits) {
|
|
498
|
+
const corrected = [...argv];
|
|
499
|
+
const jsonIndex = corrected.indexOf("--json");
|
|
500
|
+
corrected.splice(
|
|
501
|
+
jsonIndex === -1 ? corrected.length : jsonIndex,
|
|
502
|
+
0,
|
|
503
|
+
"--completed-units",
|
|
504
|
+
String(completedUnits),
|
|
505
|
+
);
|
|
506
|
+
return `owlrunkit ${corrected.map(shellCommandToken).join(" ")}`;
|
|
507
|
+
}
|
|
508
|
+
|
|
417
509
|
function formatTeamProjectAssignmentHuman(result) {
|
|
418
510
|
const disposition = result.resumed ? "Resumed" : "Assigned";
|
|
419
511
|
const nextAction = result.nextAction === null
|
|
@@ -1550,8 +1642,13 @@ function inspect(options) {
|
|
|
1550
1642
|
throw new Error(`Execution was not found: ${requestedRunId}`);
|
|
1551
1643
|
}
|
|
1552
1644
|
const onboarding = onboardingProjection(root, recovery);
|
|
1645
|
+
const projectDefinitionPath = runtimePath(root, "project", "definition.json");
|
|
1646
|
+
const projectDriver = pathEntryExists(projectDefinitionPath)
|
|
1647
|
+
? readTeamProjectStatusV1({ workspaceRoot: root })
|
|
1648
|
+
: null;
|
|
1553
1649
|
const summary = buildInspectSummary(result, {
|
|
1554
1650
|
maintenanceNextAction: onboarding.maintenanceNextAction ?? null,
|
|
1651
|
+
projectDriver,
|
|
1555
1652
|
});
|
|
1556
1653
|
const view = options.history
|
|
1557
1654
|
? { mode: "history" }
|
|
@@ -1563,7 +1660,13 @@ function inspect(options) {
|
|
|
1563
1660
|
execution: executions.find(execution => execution.runId === requestedRunId),
|
|
1564
1661
|
}
|
|
1565
1662
|
: { mode: "summary" };
|
|
1566
|
-
return {
|
|
1663
|
+
return {
|
|
1664
|
+
...result,
|
|
1665
|
+
summary,
|
|
1666
|
+
view,
|
|
1667
|
+
onboarding,
|
|
1668
|
+
...(projectDriver === null ? {} : { projectDriver }),
|
|
1669
|
+
};
|
|
1567
1670
|
}
|
|
1568
1671
|
|
|
1569
1672
|
function closeout(options) {
|
|
@@ -1784,6 +1887,7 @@ export async function runCli(argv = process.argv.slice(2), runtimeHooks = {}) {
|
|
|
1784
1887
|
"fleet",
|
|
1785
1888
|
"formal",
|
|
1786
1889
|
"lease",
|
|
1890
|
+
"mode",
|
|
1787
1891
|
"profiles",
|
|
1788
1892
|
"project",
|
|
1789
1893
|
]).has(command);
|
|
@@ -1806,7 +1910,9 @@ export async function runCli(argv = process.argv.slice(2), runtimeHooks = {}) {
|
|
|
1806
1910
|
}
|
|
1807
1911
|
}
|
|
1808
1912
|
const options = parseOptions(nested ? nestedOptions : optionValues, {
|
|
1809
|
-
multi: command === "
|
|
1913
|
+
multi: command === "mode"
|
|
1914
|
+
? ["risk-category"]
|
|
1915
|
+
: command === "profiles" && action === "impact"
|
|
1810
1916
|
? ["changed"]
|
|
1811
1917
|
: (command === "fleet" && action === "discover")
|
|
1812
1918
|
|| (command === "core-successor" && action === "plan")
|
|
@@ -1826,7 +1932,27 @@ export async function runCli(argv = process.argv.slice(2), runtimeHooks = {}) {
|
|
|
1826
1932
|
: command === "project" && action === "decision"
|
|
1827
1933
|
? ["blocking-work-item", "option", "evidence"]
|
|
1828
1934
|
: [],
|
|
1829
|
-
boolean: command === "
|
|
1935
|
+
boolean: command === "mode" && action === "recommend"
|
|
1936
|
+
? [
|
|
1937
|
+
"json",
|
|
1938
|
+
"needs-evidence",
|
|
1939
|
+
"may-mutate-project",
|
|
1940
|
+
"external-side-effects",
|
|
1941
|
+
"cross-session",
|
|
1942
|
+
"long-running",
|
|
1943
|
+
"interruption-recovery",
|
|
1944
|
+
"dirty-parallel-workspace",
|
|
1945
|
+
"irreversible",
|
|
1946
|
+
"rollback-uncertain",
|
|
1947
|
+
"permission-sensitive",
|
|
1948
|
+
"security-sensitive",
|
|
1949
|
+
"funds-at-risk",
|
|
1950
|
+
"schema-or-data-migration",
|
|
1951
|
+
"formal-acceptance",
|
|
1952
|
+
]
|
|
1953
|
+
: command === "mode" && action === "status"
|
|
1954
|
+
? ["json"]
|
|
1955
|
+
: command === "inspect"
|
|
1830
1956
|
? ["json", "verbose", "history"]
|
|
1831
1957
|
: command === "init"
|
|
1832
1958
|
? ["json"]
|
|
@@ -1887,6 +2013,97 @@ export async function runCli(argv = process.argv.slice(2), runtimeHooks = {}) {
|
|
|
1887
2013
|
const requestPath = realpathSync(requireOption(options, "request"));
|
|
1888
2014
|
return routeRunKitAssuranceV1(readJson(requestPath));
|
|
1889
2015
|
}
|
|
2016
|
+
if (command === "mode") {
|
|
2017
|
+
const root = workspace(options);
|
|
2018
|
+
if (action === "recommend") {
|
|
2019
|
+
assertOnlyOptions(options, [
|
|
2020
|
+
"workspace",
|
|
2021
|
+
"requested-mode",
|
|
2022
|
+
"needs-evidence",
|
|
2023
|
+
"exact-command-count",
|
|
2024
|
+
"writer-count",
|
|
2025
|
+
"may-mutate-project",
|
|
2026
|
+
"external-side-effects",
|
|
2027
|
+
"cross-session",
|
|
2028
|
+
"long-running",
|
|
2029
|
+
"interruption-recovery",
|
|
2030
|
+
"dirty-parallel-workspace",
|
|
2031
|
+
"irreversible",
|
|
2032
|
+
"rollback-uncertain",
|
|
2033
|
+
"permission-sensitive",
|
|
2034
|
+
"security-sensitive",
|
|
2035
|
+
"funds-at-risk",
|
|
2036
|
+
"schema-or-data-migration",
|
|
2037
|
+
"formal-acceptance",
|
|
2038
|
+
"risk-category",
|
|
2039
|
+
"json",
|
|
2040
|
+
]);
|
|
2041
|
+
const recommendation = recommendUsageModeV1({
|
|
2042
|
+
workspaceRoot: root,
|
|
2043
|
+
requestedMode: options["requested-mode"] ?? "auto",
|
|
2044
|
+
needsDurableEvidence: options["needs-evidence"] === true,
|
|
2045
|
+
exactCommandCount: parseCanonicalNonnegativeSafeInteger(
|
|
2046
|
+
options["exact-command-count"] ?? "0",
|
|
2047
|
+
"exact-command-count",
|
|
2048
|
+
),
|
|
2049
|
+
writerCount: parseCanonicalNonnegativeSafeInteger(
|
|
2050
|
+
options["writer-count"] ?? "1",
|
|
2051
|
+
"writer-count",
|
|
2052
|
+
),
|
|
2053
|
+
mayMutateProject: options["may-mutate-project"] === true,
|
|
2054
|
+
externalSideEffects: options["external-side-effects"] === true,
|
|
2055
|
+
crossSession: options["cross-session"] === true,
|
|
2056
|
+
longRunning: options["long-running"] === true,
|
|
2057
|
+
interruptionRecovery: options["interruption-recovery"] === true,
|
|
2058
|
+
dirtyParallelWorkspace: options["dirty-parallel-workspace"] === true,
|
|
2059
|
+
irreversibleChange: options.irreversible === true,
|
|
2060
|
+
rollbackUncertain: options["rollback-uncertain"] === true,
|
|
2061
|
+
permissionSensitive: options["permission-sensitive"] === true,
|
|
2062
|
+
securitySensitive: options["security-sensitive"] === true,
|
|
2063
|
+
fundsAtRisk: options["funds-at-risk"] === true,
|
|
2064
|
+
schemaOrDataMigration: options["schema-or-data-migration"] === true,
|
|
2065
|
+
needsFormalAcceptance: options["formal-acceptance"] === true,
|
|
2066
|
+
riskCategories: options["risk-category"] ?? [],
|
|
2067
|
+
});
|
|
2068
|
+
return options.json === true
|
|
2069
|
+
? recommendation
|
|
2070
|
+
: withHumanOutput(recommendation, formatUsageModeHumanV1(recommendation));
|
|
2071
|
+
}
|
|
2072
|
+
if (action === "status") {
|
|
2073
|
+
assertOnlyOptions(options, ["workspace", "json"]);
|
|
2074
|
+
const recommendation = recommendUsageModeV1({
|
|
2075
|
+
workspaceRoot: root,
|
|
2076
|
+
requestedMode: "auto",
|
|
2077
|
+
needsDurableEvidence: false,
|
|
2078
|
+
exactCommandCount: 0,
|
|
2079
|
+
writerCount: 1,
|
|
2080
|
+
mayMutateProject: false,
|
|
2081
|
+
externalSideEffects: false,
|
|
2082
|
+
crossSession: false,
|
|
2083
|
+
longRunning: false,
|
|
2084
|
+
interruptionRecovery: false,
|
|
2085
|
+
dirtyParallelWorkspace: false,
|
|
2086
|
+
irreversibleChange: false,
|
|
2087
|
+
rollbackUncertain: false,
|
|
2088
|
+
permissionSensitive: false,
|
|
2089
|
+
securitySensitive: false,
|
|
2090
|
+
fundsAtRisk: false,
|
|
2091
|
+
schemaOrDataMigration: false,
|
|
2092
|
+
needsFormalAcceptance: false,
|
|
2093
|
+
riskCategories: [],
|
|
2094
|
+
});
|
|
2095
|
+
const status = {
|
|
2096
|
+
...recommendation,
|
|
2097
|
+
status: "mode_status",
|
|
2098
|
+
preferenceSource: "default_auto",
|
|
2099
|
+
persistedPreference: null,
|
|
2100
|
+
};
|
|
2101
|
+
return options.json === true
|
|
2102
|
+
? status
|
|
2103
|
+
: withHumanOutput(status, formatUsageModeHumanV1(status));
|
|
2104
|
+
}
|
|
2105
|
+
throw new Error("Usage: owlrunkit mode <recommend|status> --workspace <directory>");
|
|
2106
|
+
}
|
|
1890
2107
|
if (command === "fleet") {
|
|
1891
2108
|
if (action === "register-root") {
|
|
1892
2109
|
const registered = await registerFleetCoverageRoot({
|
|
@@ -2897,24 +3114,49 @@ export async function runCli(argv = process.argv.slice(2), runtimeHooks = {}) {
|
|
|
2897
3114
|
event: requestedEvent,
|
|
2898
3115
|
});
|
|
2899
3116
|
}
|
|
3117
|
+
const checkpointId = requireOption(options, "checkpoint-id");
|
|
3118
|
+
const occurredAt = requireOption(options, "at");
|
|
3119
|
+
const assignmentId = requireOption(options, "assignment-id");
|
|
3120
|
+
const workItemId = requireOption(options, "work-item");
|
|
3121
|
+
const state = requireOption(options, "state");
|
|
3122
|
+
const summary = requireOption(options, "summary");
|
|
2900
3123
|
const completedUnits = options["completed-units"] === undefined
|
|
2901
3124
|
? undefined
|
|
2902
3125
|
: parseCanonicalNonnegativeSafeInteger(options["completed-units"], "completed-units");
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
3126
|
+
let checkpointed;
|
|
3127
|
+
try {
|
|
3128
|
+
checkpointed = checkpointTeamProjectV1({
|
|
3129
|
+
workspaceRoot: root,
|
|
3130
|
+
checkpointId,
|
|
3131
|
+
occurredAt,
|
|
3132
|
+
assignmentId,
|
|
3133
|
+
workItemId,
|
|
3134
|
+
state,
|
|
3135
|
+
summary,
|
|
3136
|
+
completedUnits,
|
|
3137
|
+
evidenceRefs: options.evidence ?? [],
|
|
3138
|
+
blockerRefs: options.blocker ?? [],
|
|
3139
|
+
decisionRefs: options.decision ?? [],
|
|
3140
|
+
nextAction: options.next ?? null,
|
|
3141
|
+
sourceFingerprint: options["source-fingerprint"],
|
|
3142
|
+
});
|
|
3143
|
+
} catch (error) {
|
|
3144
|
+
if (error instanceof Error
|
|
3145
|
+
&& error.message === "Measured work item checkpoint requires completedUnits.") {
|
|
3146
|
+
const projectStatus = readTeamProjectStatusV1({ workspaceRoot: root });
|
|
3147
|
+
const workItem = projectStatus.workItems.find((row) => row.workItemId === workItemId);
|
|
3148
|
+
const suggestedUnits = state === "completed"
|
|
3149
|
+
? workItem?.progress.total
|
|
3150
|
+
: workItem?.progress.completed;
|
|
3151
|
+
if (Number.isSafeInteger(suggestedUnits)) {
|
|
3152
|
+
throw new Error(
|
|
3153
|
+
"Measured work item checkpoint requires --completed-units. "
|
|
3154
|
+
+ `Corrected command: ${correctedCheckpointCommand(argv, suggestedUnits)}`,
|
|
3155
|
+
);
|
|
3156
|
+
}
|
|
3157
|
+
}
|
|
3158
|
+
throw error;
|
|
3159
|
+
}
|
|
2918
3160
|
return options.json === true
|
|
2919
3161
|
? checkpointed
|
|
2920
3162
|
: withHumanOutput(checkpointed, formatTeamProjectCheckpointHuman(checkpointed));
|
|
@@ -3002,13 +3244,25 @@ export async function runCli(argv = process.argv.slice(2), runtimeHooks = {}) {
|
|
|
3002
3244
|
? applyDetectedProfilesV2({ workspaceRoot: root, detection })
|
|
3003
3245
|
: detection;
|
|
3004
3246
|
}
|
|
3247
|
+
if (action === "reconcile") {
|
|
3248
|
+
if (options.apply === true && options["dry-run"] === true) {
|
|
3249
|
+
throw new Error("profiles reconcile accepts either --apply or --dry-run, not both.");
|
|
3250
|
+
}
|
|
3251
|
+
if (options.apply !== true && options["dry-run"] !== true) {
|
|
3252
|
+
throw new Error("profiles reconcile requires --dry-run or --apply.");
|
|
3253
|
+
}
|
|
3254
|
+
return reconcileProfilesV2({
|
|
3255
|
+
workspaceRoot: root,
|
|
3256
|
+
apply: options.apply === true,
|
|
3257
|
+
});
|
|
3258
|
+
}
|
|
3005
3259
|
if (action === "validate") return validateProfiles({ workspaceRoot: root });
|
|
3006
3260
|
if (action === "impact") {
|
|
3007
3261
|
const changedPaths = options.changed ?? [];
|
|
3008
3262
|
if (changedPaths.length === 0) throw new Error("profiles impact requires --changed.");
|
|
3009
3263
|
return resolveProfilesImpact({ workspaceRoot: root, changedPaths });
|
|
3010
3264
|
}
|
|
3011
|
-
throw new Error("Usage: owlrunkit profiles <detect|validate|impact> [options]");
|
|
3265
|
+
throw new Error("Usage: owlrunkit profiles <detect|reconcile|validate|impact> [options]");
|
|
3012
3266
|
}
|
|
3013
3267
|
if (command === "lease") {
|
|
3014
3268
|
const root = workspace(options);
|
|
@@ -3151,7 +3405,7 @@ export async function runCli(argv = process.argv.slice(2), runtimeHooks = {}) {
|
|
|
3151
3405
|
return withControlTransaction(root, () => closeout(options));
|
|
3152
3406
|
}
|
|
3153
3407
|
if (command === "finish") return finishExecution(options);
|
|
3154
|
-
throw new Error("Usage: owlrunkit <init|doctor|adopt|assurance|fleet|profiles|project|status|formal|candidate|deployment|core-successor|start|verify|repair|offline-export|offline-import|quick-verify|quick-attest|quick-metrics|finish|plan|inspect|lease|delivery|coverage-adopt|resume|verify-plan|resource-preflight|snapshot|visual-smoke|finalize|ready-for-commit|closeout> [options]");
|
|
3408
|
+
throw new Error("Usage: owlrunkit <init|doctor|adopt|mode|assurance|fleet|profiles|project|status|formal|candidate|deployment|core-successor|start|verify|repair|offline-export|offline-import|quick-verify|quick-attest|quick-metrics|finish|plan|inspect|lease|delivery|coverage-adopt|resume|verify-plan|resource-preflight|snapshot|visual-smoke|finalize|ready-for-commit|closeout> [options]");
|
|
3155
3409
|
} catch (error) {
|
|
3156
3410
|
return {
|
|
3157
3411
|
status: "invalid_input",
|