@deftai/directive 0.103.0 → 0.105.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/dist/authz.d.ts +4 -29
- package/dist/authz.js +16 -181
- package/dist/dispatch.d.ts +1 -1
- package/dist/dispatch.js +16 -0
- package/dist/human-presence-mint.d.ts +61 -0
- package/dist/human-presence-mint.js +192 -0
- package/dist/occupancy-steal.d.ts +9 -0
- package/dist/occupancy-steal.js +54 -0
- package/dist/scm-sync-default.d.ts +34 -0
- package/dist/scm-sync-default.js +210 -0
- package/dist/scope-record-approved-scope.d.ts +6 -1
- package/dist/scope-record-approved-scope.js +72 -24
- package/dist/session-start.d.ts +4 -0
- package/dist/session-start.js +23 -0
- package/dist/verify-ac.d.ts +2 -0
- package/dist/verify-ac.js +74 -8
- package/dist/verify-completed-tracked.d.ts +2 -1
- package/dist/verify-completed-tracked.js +31 -1
- package/dist/verify-literal-ac.js +7 -0
- package/dist/verify-orphan-active.d.ts +1 -0
- package/dist/verify-orphan-active.js +30 -0
- package/dist/verify-session-ritual.d.ts +10 -2
- package/dist/verify-session-ritual.js +16 -3
- package/dist/verify-source-cli/verify-telemetry-coverage.d.ts +13 -0
- package/dist/verify-source-cli/verify-telemetry-coverage.js +67 -0
- package/package.json +3 -3
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { stealOccupancy } from "@deftai/directive-core/session";
|
|
4
|
+
export function parseArgs(argv) {
|
|
5
|
+
const parsed = { projectRoot: ".", confirm: false, occupant: null };
|
|
6
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
7
|
+
const arg = argv[i];
|
|
8
|
+
if (arg === "--confirm") {
|
|
9
|
+
parsed.confirm = true;
|
|
10
|
+
}
|
|
11
|
+
else if (arg === "--occupant") {
|
|
12
|
+
const value = argv[i + 1];
|
|
13
|
+
if (value === undefined) {
|
|
14
|
+
return { ...parsed, error: "argument --occupant: expected one argument" };
|
|
15
|
+
}
|
|
16
|
+
parsed.occupant = value;
|
|
17
|
+
i += 1;
|
|
18
|
+
}
|
|
19
|
+
else if (arg?.startsWith("--occupant=")) {
|
|
20
|
+
parsed.occupant = arg.slice("--occupant=".length);
|
|
21
|
+
}
|
|
22
|
+
else if (arg === "--project-root") {
|
|
23
|
+
const value = argv[i + 1];
|
|
24
|
+
if (value === undefined) {
|
|
25
|
+
return { ...parsed, error: "argument --project-root: expected one argument" };
|
|
26
|
+
}
|
|
27
|
+
parsed.projectRoot = value;
|
|
28
|
+
i += 1;
|
|
29
|
+
}
|
|
30
|
+
else if (arg?.startsWith("--project-root=")) {
|
|
31
|
+
parsed.projectRoot = arg.slice("--project-root=".length);
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
return { ...parsed, error: `unrecognized argument: ${arg}` };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return parsed;
|
|
38
|
+
}
|
|
39
|
+
export function run(argv) {
|
|
40
|
+
const args = parseArgs(argv);
|
|
41
|
+
if (args.error !== undefined) {
|
|
42
|
+
process.stderr.write(`occupancy:steal: ${args.error}\n`);
|
|
43
|
+
return 2;
|
|
44
|
+
}
|
|
45
|
+
const result = stealOccupancy(resolve(args.projectRoot), {
|
|
46
|
+
confirm: args.confirm,
|
|
47
|
+
occupant: args.occupant ?? undefined,
|
|
48
|
+
env: process.env,
|
|
49
|
+
});
|
|
50
|
+
const sink = result.code === 0 ? process.stdout : process.stderr;
|
|
51
|
+
sink.write(`${result.message}\n`);
|
|
52
|
+
return result.code;
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=occupancy-steal.js.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI for `scm:sync-default` (#3391).
|
|
3
|
+
*
|
|
4
|
+
* Opens dest-targeted default-branch sync PRs using the shared detector and
|
|
5
|
+
* syncMaxFiles. Over-limit legs are new branches and new PRs.
|
|
6
|
+
*/
|
|
7
|
+
import { planSyncDefault, type SyncDefaultForge, type SyncDefaultOpenPull } from "@deftai/directive-core/policy";
|
|
8
|
+
export declare const USAGE: string;
|
|
9
|
+
export interface SyncDefaultCliArgs {
|
|
10
|
+
readonly dryRun?: boolean;
|
|
11
|
+
readonly json?: boolean;
|
|
12
|
+
readonly help?: boolean;
|
|
13
|
+
readonly maxFiles?: number;
|
|
14
|
+
readonly projectRoot?: string;
|
|
15
|
+
readonly repo?: string;
|
|
16
|
+
}
|
|
17
|
+
export declare function parseSyncDefaultArgs(argv: readonly string[]): {
|
|
18
|
+
args: SyncDefaultCliArgs;
|
|
19
|
+
error: string | null;
|
|
20
|
+
};
|
|
21
|
+
export declare function pullsFromRestJson(payload: unknown): readonly SyncDefaultOpenPull[];
|
|
22
|
+
export declare function createGhSyncDefaultForge(): SyncDefaultForge;
|
|
23
|
+
export declare function resolveRepoFromGit(projectRoot: string): string | null;
|
|
24
|
+
export declare function runSyncDefaultCli(args: SyncDefaultCliArgs, options?: {
|
|
25
|
+
writeOut?: (s: string) => void;
|
|
26
|
+
writeErr?: (s: string) => void;
|
|
27
|
+
forge?: SyncDefaultForge;
|
|
28
|
+
cwd?: string;
|
|
29
|
+
runGit?: Parameters<typeof planSyncDefault>[0]["runGit"];
|
|
30
|
+
resolveRepo?: (projectRoot: string) => string | null;
|
|
31
|
+
}): number;
|
|
32
|
+
export declare function mainEntry(argv?: string[]): number;
|
|
33
|
+
export declare function main(argv?: string[]): number;
|
|
34
|
+
//# sourceMappingURL=scm-sync-default.d.ts.map
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI for `scm:sync-default` (#3391).
|
|
3
|
+
*
|
|
4
|
+
* Opens dest-targeted default-branch sync PRs using the shared detector and
|
|
5
|
+
* syncMaxFiles. Over-limit legs are new branches and new PRs.
|
|
6
|
+
*/
|
|
7
|
+
import { writeFileSync } from "node:fs";
|
|
8
|
+
import { tmpdir } from "node:os";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
11
|
+
import { applySyncDefault, formatSyncDefaultHuman, parseGithubOwnerRepo, planSyncDefault, SYNC_DEFAULT_VERB, } from "@deftai/directive-core/policy";
|
|
12
|
+
import { runGhApi, splitRepo } from "@deftai/directive-core/scm";
|
|
13
|
+
import { defaultGitRunner } from "@deftai/directive-core/session";
|
|
14
|
+
export const USAGE = `usage: deft ${SYNC_DEFAULT_VERB} [--dry-run] [--json] [--max-files N] [--repo OWNER/REPO]\n` +
|
|
15
|
+
" Open dest-targeted sync PRs under syncMaxFiles. Under the limit: one new PR\n" +
|
|
16
|
+
" from source tip to dest. Over: merge-commit cuts; each dest-based leg is a\n" +
|
|
17
|
+
" new branch and a new PR. After a leg merges, run again. Never retarget or\n" +
|
|
18
|
+
" reuse an oversized PR. Each leg must be new when the reviewer first sees it.\n" +
|
|
19
|
+
" Required checks stay on except the Wave 1 core-guard sync exemption.\n";
|
|
20
|
+
export function parseSyncDefaultArgs(argv) {
|
|
21
|
+
const out = {};
|
|
22
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
23
|
+
const arg = argv[i];
|
|
24
|
+
if (arg === "--dry-run") {
|
|
25
|
+
out.dryRun = true;
|
|
26
|
+
}
|
|
27
|
+
else if (arg === "--json") {
|
|
28
|
+
out.json = true;
|
|
29
|
+
}
|
|
30
|
+
else if (arg === "--help" || arg === "-h") {
|
|
31
|
+
out.help = true;
|
|
32
|
+
}
|
|
33
|
+
else if (arg === "--max-files") {
|
|
34
|
+
const raw = argv[++i];
|
|
35
|
+
const parsed = raw === undefined ? Number.NaN : Number(raw);
|
|
36
|
+
if (!Number.isInteger(parsed) || parsed < 0) {
|
|
37
|
+
return { args: out, error: `--max-files expects a non-negative integer, got ${raw ?? ""}` };
|
|
38
|
+
}
|
|
39
|
+
out.maxFiles = parsed;
|
|
40
|
+
}
|
|
41
|
+
else if (arg === "--project-root") {
|
|
42
|
+
const value = argv[++i];
|
|
43
|
+
if (value === undefined) {
|
|
44
|
+
return { args: out, error: "--project-root expects a path" };
|
|
45
|
+
}
|
|
46
|
+
out.projectRoot = value;
|
|
47
|
+
}
|
|
48
|
+
else if (arg === "--repo") {
|
|
49
|
+
const value = argv[++i];
|
|
50
|
+
if (value === undefined) {
|
|
51
|
+
return { args: out, error: "--repo expects OWNER/REPO" };
|
|
52
|
+
}
|
|
53
|
+
out.repo = value;
|
|
54
|
+
}
|
|
55
|
+
else if (arg.startsWith("-")) {
|
|
56
|
+
return { args: out, error: `unknown flag ${JSON.stringify(arg)}` };
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
return { args: out, error: `unexpected argument ${JSON.stringify(arg)}` };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return { args: out, error: null };
|
|
63
|
+
}
|
|
64
|
+
export function pullsFromRestJson(payload) {
|
|
65
|
+
if (!Array.isArray(payload))
|
|
66
|
+
return [];
|
|
67
|
+
const pulls = [];
|
|
68
|
+
for (const row of payload) {
|
|
69
|
+
if (typeof row !== "object" || row === null)
|
|
70
|
+
continue;
|
|
71
|
+
const rec = row;
|
|
72
|
+
const head = rec.head;
|
|
73
|
+
const baseRec = rec.base;
|
|
74
|
+
if (typeof head !== "object" || head === null)
|
|
75
|
+
continue;
|
|
76
|
+
if (typeof baseRec !== "object" || baseRec === null)
|
|
77
|
+
continue;
|
|
78
|
+
const headRec = head;
|
|
79
|
+
const baseObj = baseRec;
|
|
80
|
+
const number = rec.number;
|
|
81
|
+
const htmlUrl = rec.html_url;
|
|
82
|
+
const headRef = headRec.ref;
|
|
83
|
+
const headSha = headRec.sha;
|
|
84
|
+
const baseRef = baseObj.ref;
|
|
85
|
+
if (typeof number !== "number" ||
|
|
86
|
+
typeof htmlUrl !== "string" ||
|
|
87
|
+
typeof headRef !== "string" ||
|
|
88
|
+
typeof headSha !== "string" ||
|
|
89
|
+
typeof baseRef !== "string") {
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
pulls.push({ number, htmlUrl, headRef, headSha, baseRef });
|
|
93
|
+
}
|
|
94
|
+
return pulls;
|
|
95
|
+
}
|
|
96
|
+
export function createGhSyncDefaultForge() {
|
|
97
|
+
return {
|
|
98
|
+
listOpenPulls(repo, base) {
|
|
99
|
+
const [owner, name] = splitRepo(repo);
|
|
100
|
+
const endpoint = `repos/${owner}/${name}/pulls`;
|
|
101
|
+
const result = runGhApi([
|
|
102
|
+
endpoint,
|
|
103
|
+
"--method",
|
|
104
|
+
"GET",
|
|
105
|
+
"--raw-field",
|
|
106
|
+
`base=${base}`,
|
|
107
|
+
"--raw-field",
|
|
108
|
+
"state=open",
|
|
109
|
+
"--raw-field",
|
|
110
|
+
"per_page=100",
|
|
111
|
+
]);
|
|
112
|
+
if (result.returncode !== 0) {
|
|
113
|
+
throw new Error(result.stderr || `failed to list open pulls for ${repo}`);
|
|
114
|
+
}
|
|
115
|
+
return pullsFromRestJson(JSON.parse(result.stdout || "[]"));
|
|
116
|
+
},
|
|
117
|
+
createPull(repo, input) {
|
|
118
|
+
const [owner, name] = splitRepo(repo);
|
|
119
|
+
const endpoint = `repos/${owner}/${name}/pulls`;
|
|
120
|
+
const bodyPath = join(tmpdir(), `deft-sync-default-${Date.now()}.json`);
|
|
121
|
+
writeFileSync(bodyPath, JSON.stringify({
|
|
122
|
+
title: input.title,
|
|
123
|
+
head: input.head,
|
|
124
|
+
base: input.base,
|
|
125
|
+
body: input.body,
|
|
126
|
+
}), { encoding: "utf8" });
|
|
127
|
+
const result = runGhApi(["-X", "POST", endpoint, "--input", bodyPath]);
|
|
128
|
+
if (result.returncode !== 0) {
|
|
129
|
+
throw new Error(result.stderr || `failed to create pull for ${repo}`);
|
|
130
|
+
}
|
|
131
|
+
const created = JSON.parse(result.stdout);
|
|
132
|
+
if (typeof created.number !== "number" || typeof created.html_url !== "string") {
|
|
133
|
+
throw new Error("create pull returned no number/html_url");
|
|
134
|
+
}
|
|
135
|
+
return { number: created.number, htmlUrl: created.html_url };
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
export function resolveRepoFromGit(projectRoot) {
|
|
140
|
+
const remote = defaultGitRunner(projectRoot, ["remote", "get-url", "origin"]);
|
|
141
|
+
if (remote.code !== 0)
|
|
142
|
+
return null;
|
|
143
|
+
return parseGithubOwnerRepo(remote.stdout);
|
|
144
|
+
}
|
|
145
|
+
export function runSyncDefaultCli(args, options = {}) {
|
|
146
|
+
const writeOut = options.writeOut ?? ((s) => process.stdout.write(s));
|
|
147
|
+
const writeErr = options.writeErr ?? ((s) => process.stderr.write(s));
|
|
148
|
+
if (args.help === true) {
|
|
149
|
+
writeOut(USAGE);
|
|
150
|
+
return 0;
|
|
151
|
+
}
|
|
152
|
+
const projectRoot = args.projectRoot ?? options.cwd ?? process.cwd();
|
|
153
|
+
const plan = planSyncDefault({
|
|
154
|
+
projectRoot,
|
|
155
|
+
maxFiles: args.maxFiles,
|
|
156
|
+
runGit: options.runGit,
|
|
157
|
+
});
|
|
158
|
+
if (plan.action === "noop") {
|
|
159
|
+
if (args.json === true) {
|
|
160
|
+
writeOut(`${JSON.stringify({ ...plan, opened: [], retargeted: false }, null, 2)}\n`);
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
writeOut(`${plan.message}\n`);
|
|
164
|
+
}
|
|
165
|
+
return plan.noopReason === "fetch-failed" || plan.noopReason === "diff-failed" ? 2 : 0;
|
|
166
|
+
}
|
|
167
|
+
const resolveRepo = options.resolveRepo ?? resolveRepoFromGit;
|
|
168
|
+
const repo = args.repo ?? resolveRepo(projectRoot);
|
|
169
|
+
if (repo === null && args.dryRun !== true) {
|
|
170
|
+
writeErr("scm:sync-default: could not resolve OWNER/REPO from origin; pass --repo\n");
|
|
171
|
+
return 2;
|
|
172
|
+
}
|
|
173
|
+
try {
|
|
174
|
+
const result = applySyncDefault({
|
|
175
|
+
projectRoot,
|
|
176
|
+
repo: repo ?? "owner/repo",
|
|
177
|
+
plan,
|
|
178
|
+
dryRun: args.dryRun === true,
|
|
179
|
+
runGit: options.runGit,
|
|
180
|
+
forge: args.dryRun === true ? undefined : (options.forge ?? createGhSyncDefaultForge()),
|
|
181
|
+
});
|
|
182
|
+
if (args.json === true) {
|
|
183
|
+
writeOut(`${JSON.stringify(result, null, 2)}\n`);
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
writeOut(formatSyncDefaultHuman(result));
|
|
187
|
+
}
|
|
188
|
+
return 0;
|
|
189
|
+
}
|
|
190
|
+
catch (err) {
|
|
191
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
192
|
+
writeErr(`scm:sync-default: ${message}\n`);
|
|
193
|
+
return 1;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
export function mainEntry(argv = process.argv.slice(2)) {
|
|
197
|
+
const { args, error } = parseSyncDefaultArgs(argv);
|
|
198
|
+
if (error !== null) {
|
|
199
|
+
process.stderr.write(`error: ${error}\n${USAGE}`);
|
|
200
|
+
return 2;
|
|
201
|
+
}
|
|
202
|
+
return runSyncDefaultCli(args);
|
|
203
|
+
}
|
|
204
|
+
export function main(argv = process.argv.slice(2)) {
|
|
205
|
+
return mainEntry(argv);
|
|
206
|
+
}
|
|
207
|
+
if (process.argv[1] !== undefined && fileURLToPath(import.meta.url) === process.argv[1]) {
|
|
208
|
+
process.exit(mainEntry());
|
|
209
|
+
}
|
|
210
|
+
//# sourceMappingURL=scm-sync-default.js.map
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { type HumanPresenceMintSeams } from "./human-presence-mint.js";
|
|
2
3
|
/** True when `candidate` is the same as or a descendant of `root` after realpath. */
|
|
3
4
|
export declare function isPathInsideRoot(root: string, candidate: string): boolean;
|
|
4
5
|
export interface ParsedArgs {
|
|
@@ -9,6 +10,10 @@ export interface ParsedArgs {
|
|
|
9
10
|
/** Optional override for recorded xbriefRelPath (defaults: pending→active map). */
|
|
10
11
|
xbriefRelPath: string;
|
|
11
12
|
quiet: boolean;
|
|
13
|
+
/** Explicit operator confirm for the #3110 human-presence mint (#3384). */
|
|
14
|
+
confirm: boolean;
|
|
15
|
+
/** Optional owner/name seed for preimage approvedRepos (#3385 R5). */
|
|
16
|
+
repo: string;
|
|
12
17
|
error?: string;
|
|
13
18
|
}
|
|
14
19
|
/**
|
|
@@ -18,5 +23,5 @@ export interface ParsedArgs {
|
|
|
18
23
|
*/
|
|
19
24
|
export declare function resolveApprovalXbriefRelPath(sourceRelOrAbs: string, projectRoot: string, override?: string): string | null;
|
|
20
25
|
export declare function parseArgs(argv: string[]): ParsedArgs;
|
|
21
|
-
export declare function run(argv: string[]): number;
|
|
26
|
+
export declare function run(argv: string[], seams?: HumanPresenceMintSeams): number;
|
|
22
27
|
//# sourceMappingURL=scope-record-approved-scope.d.ts.map
|
|
@@ -9,8 +9,9 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { existsSync, lstatSync, readFileSync, realpathSync } from "node:fs";
|
|
11
11
|
import { basename, join, relative, resolve, sep } from "node:path";
|
|
12
|
-
import { scopeProvenance } from "@deftai/directive-core";
|
|
12
|
+
import { scopeProvenance, slice } from "@deftai/directive-core";
|
|
13
13
|
import { isDirectEntrypoint } from "./entrypoint.js";
|
|
14
|
+
import { refuseMintWhileUatActive, refuseNonInteractiveMint, resolveHumanPresenceMintSeams, } from "./human-presence-mint.js";
|
|
14
15
|
/** True when `candidate` is the same as or a descendant of `root` after realpath. */
|
|
15
16
|
export function isPathInsideRoot(root, candidate) {
|
|
16
17
|
let rootReal;
|
|
@@ -37,13 +38,13 @@ export function isPathInsideRoot(root, candidate) {
|
|
|
37
38
|
return false;
|
|
38
39
|
return !rel.startsWith("..");
|
|
39
40
|
}
|
|
40
|
-
const {
|
|
41
|
+
const { isHumanApprovalStamp, mintApprovedScopeArtifacts } = scopeProvenance;
|
|
41
42
|
function usage() {
|
|
42
|
-
return ("usage: scope:record-approved-scope -- <xbrief-path> --actor <name> " +
|
|
43
|
-
"[--kind operator] [--project-root <dir>] [--xbrief-rel-path <posix>] [--quiet]\n" +
|
|
44
|
-
" Writes .deft/approved-scope/<plan-id>.json
|
|
45
|
-
"
|
|
46
|
-
"
|
|
43
|
+
return ("usage: scope:record-approved-scope -- <xbrief-path> --actor <name> --confirm " +
|
|
44
|
+
"[--kind operator] [--project-root <dir>] [--xbrief-rel-path <posix>] [--repo owner/name] [--quiet]\n" +
|
|
45
|
+
" Writes .deft/approved-scope/<plan-id>.json and <plan-id>.intent.json (#3205 / #3384 / #3385).\n" +
|
|
46
|
+
" --actor is display only and never authorizes mint. Mint requires a real TTY, " +
|
|
47
|
+
"controlling terminal, --confirm, and typed phrase mint (#3110). Agent/CI shells refuse.");
|
|
47
48
|
}
|
|
48
49
|
/**
|
|
49
50
|
* Map pending/ → active/ for path-bound approval records.
|
|
@@ -96,6 +97,8 @@ export function parseArgs(argv) {
|
|
|
96
97
|
kind: "operator",
|
|
97
98
|
xbriefRelPath: "",
|
|
98
99
|
quiet: false,
|
|
100
|
+
confirm: false,
|
|
101
|
+
repo: "",
|
|
99
102
|
};
|
|
100
103
|
const positionals = [];
|
|
101
104
|
for (let i = 0; i < argv.length; i += 1) {
|
|
@@ -106,6 +109,9 @@ export function parseArgs(argv) {
|
|
|
106
109
|
if (arg === "--quiet") {
|
|
107
110
|
parsed.quiet = true;
|
|
108
111
|
}
|
|
112
|
+
else if (arg === "--confirm") {
|
|
113
|
+
parsed.confirm = true;
|
|
114
|
+
}
|
|
109
115
|
else if (arg === "--project-root") {
|
|
110
116
|
const value = argv[i + 1];
|
|
111
117
|
if (value === undefined) {
|
|
@@ -150,6 +156,17 @@ export function parseArgs(argv) {
|
|
|
150
156
|
else if (arg?.startsWith("--xbrief-rel-path=")) {
|
|
151
157
|
parsed.xbriefRelPath = arg.slice("--xbrief-rel-path=".length);
|
|
152
158
|
}
|
|
159
|
+
else if (arg === "--repo") {
|
|
160
|
+
const value = argv[i + 1];
|
|
161
|
+
if (value === undefined) {
|
|
162
|
+
return { ...parsed, error: "argument --repo: expected one argument" };
|
|
163
|
+
}
|
|
164
|
+
parsed.repo = value;
|
|
165
|
+
i += 1;
|
|
166
|
+
}
|
|
167
|
+
else if (arg?.startsWith("--repo=")) {
|
|
168
|
+
parsed.repo = arg.slice("--repo=".length);
|
|
169
|
+
}
|
|
153
170
|
else if (arg?.startsWith("-")) {
|
|
154
171
|
return { ...parsed, error: `unrecognized argument: ${arg}` };
|
|
155
172
|
}
|
|
@@ -172,7 +189,7 @@ export function parseArgs(argv) {
|
|
|
172
189
|
}
|
|
173
190
|
return parsed;
|
|
174
191
|
}
|
|
175
|
-
export function run(argv) {
|
|
192
|
+
export function run(argv, seams = {}) {
|
|
176
193
|
const args = parseArgs(argv);
|
|
177
194
|
if (args.error !== undefined) {
|
|
178
195
|
process.stderr.write(`scope_record_approved_scope: ${args.error}\n`);
|
|
@@ -202,14 +219,27 @@ export function run(argv) {
|
|
|
202
219
|
process.stderr.write(`scope_record_approved_scope: failed to read xBRIEF: ${String(err)}\n`);
|
|
203
220
|
return 2;
|
|
204
221
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
}
|
|
209
|
-
catch {
|
|
210
|
-
process.stderr.write("scope_record_approved_scope: xBRIEF is not valid JSON\n");
|
|
222
|
+
const parsed = scopeProvenance.parseJsonRejectingDuplicateKeys(raw);
|
|
223
|
+
if (!parsed.ok) {
|
|
224
|
+
process.stderr.write(`scope_record_approved_scope: ${parsed.error}\n`);
|
|
211
225
|
return 2;
|
|
212
226
|
}
|
|
227
|
+
const payload = parsed.value;
|
|
228
|
+
const verb = "scope:record-approved-scope";
|
|
229
|
+
const uatBlocked = refuseMintWhileUatActive(verb, projectRoot);
|
|
230
|
+
if (uatBlocked !== null)
|
|
231
|
+
return uatBlocked;
|
|
232
|
+
const resolved = resolveHumanPresenceMintSeams(seams);
|
|
233
|
+
const mintBlocked = refuseNonInteractiveMint({
|
|
234
|
+
verb,
|
|
235
|
+
confirm: args.confirm,
|
|
236
|
+
isTty: resolved.isTty,
|
|
237
|
+
environ: resolved.environ,
|
|
238
|
+
hasControllingTerminal: resolved.hasControllingTerminal,
|
|
239
|
+
readInteractiveConfirm: resolved.readInteractiveConfirm,
|
|
240
|
+
});
|
|
241
|
+
if (mintBlocked !== null)
|
|
242
|
+
return mintBlocked;
|
|
213
243
|
const stamp = {
|
|
214
244
|
kind: args.kind.trim(),
|
|
215
245
|
actor: args.actor.trim(),
|
|
@@ -228,22 +258,40 @@ export function run(argv) {
|
|
|
228
258
|
"(repo-relative path required for approval binding) (#3205).\n");
|
|
229
259
|
return 2;
|
|
230
260
|
}
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
261
|
+
const repoSeed = slice.resolveProjectRepo(args.repo.trim().length > 0 ? args.repo.trim() : undefined, projectRoot);
|
|
262
|
+
// Mint writes record + preimage as one fail-closed pair. A dest-write
|
|
263
|
+
// failure restores the prior pair or leaves neither dest (#3385 residual).
|
|
264
|
+
let minted;
|
|
265
|
+
try {
|
|
266
|
+
minted = mintApprovedScopeArtifacts({
|
|
267
|
+
xbriefRelPath,
|
|
268
|
+
payload,
|
|
269
|
+
rawText: raw,
|
|
270
|
+
projectRoot,
|
|
271
|
+
humanApproval: stamp,
|
|
272
|
+
extract: {
|
|
273
|
+
projectRoot,
|
|
274
|
+
approvedReposSeed: repoSeed !== null ? [repoSeed] : [],
|
|
275
|
+
},
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
catch (err) {
|
|
279
|
+
process.stderr.write(`scope_record_approved_scope: ${String(err)}\n`);
|
|
280
|
+
return 1;
|
|
281
|
+
}
|
|
282
|
+
const { record, recordPath, intentPath } = minted;
|
|
238
283
|
if (!args.quiet) {
|
|
239
|
-
process.stdout.write(`scope_record_approved_scope: wrote ${
|
|
284
|
+
process.stdout.write(`scope_record_approved_scope: wrote ${recordPath}\n` +
|
|
285
|
+
` preimage: ${intentPath}\n` +
|
|
240
286
|
` planId: ${record.planId}\n` +
|
|
241
287
|
` xbriefRelPath: ${record.xbriefRelPath}\n` +
|
|
242
288
|
` fileScopeDigest: ${record.fileScopeDigest}\n` +
|
|
289
|
+
` intentDigest: ${record.intentDigest ?? ""}\n` +
|
|
243
290
|
` paths: ${record.fileScope.length}\n` +
|
|
244
291
|
` humanApproval: ${stamp.kind}/${stamp.actor}\n` +
|
|
245
|
-
"
|
|
246
|
-
"
|
|
292
|
+
" Read the preimage before you commit. That file is the approved intent (#3385).\n" +
|
|
293
|
+
" Next: commit record + preimage on the merge base (or a prior PR) before " +
|
|
294
|
+
"activation/expansion in the implementation change set (#3205 / #3385).\n");
|
|
247
295
|
}
|
|
248
296
|
return 0;
|
|
249
297
|
}
|
package/dist/session-start.d.ts
CHANGED
|
@@ -32,6 +32,10 @@ export interface ParsedSessionStartArgs {
|
|
|
32
32
|
* the default; also settable via DEFT_SESSION_COMPACT=1.
|
|
33
33
|
*/
|
|
34
34
|
compact: boolean;
|
|
35
|
+
/** #3433: steal the worktree occupancy lease. */
|
|
36
|
+
steal: boolean;
|
|
37
|
+
confirm: boolean;
|
|
38
|
+
occupant: string | null;
|
|
35
39
|
error?: string;
|
|
36
40
|
}
|
|
37
41
|
/** Parse session:start CLI args, mirroring scripts/session_start.py. */
|
package/dist/session-start.js
CHANGED
|
@@ -31,6 +31,9 @@ export function parseArgs(argv) {
|
|
|
31
31
|
ceremonyDepthOverride: null,
|
|
32
32
|
effortBudgetHost: {},
|
|
33
33
|
compact: false,
|
|
34
|
+
steal: false,
|
|
35
|
+
confirm: false,
|
|
36
|
+
occupant: null,
|
|
34
37
|
};
|
|
35
38
|
const dialInputs = {};
|
|
36
39
|
for (let i = 0; i < argv.length; i += 1) {
|
|
@@ -273,6 +276,23 @@ export function parseArgs(argv) {
|
|
|
273
276
|
// #3286: terse machine orientation output (opt-in; verbose remains default)
|
|
274
277
|
parsed.compact = true;
|
|
275
278
|
}
|
|
279
|
+
else if (arg === "--steal") {
|
|
280
|
+
parsed.steal = true;
|
|
281
|
+
}
|
|
282
|
+
else if (arg === "--confirm") {
|
|
283
|
+
parsed.confirm = true;
|
|
284
|
+
}
|
|
285
|
+
else if (arg === "--occupant") {
|
|
286
|
+
const value = argv[i + 1];
|
|
287
|
+
if (value === undefined) {
|
|
288
|
+
return { ...parsed, error: "argument --occupant: expected one argument" };
|
|
289
|
+
}
|
|
290
|
+
parsed.occupant = value;
|
|
291
|
+
i += 1;
|
|
292
|
+
}
|
|
293
|
+
else if (arg?.startsWith("--occupant=")) {
|
|
294
|
+
parsed.occupant = arg.slice("--occupant=".length);
|
|
295
|
+
}
|
|
276
296
|
else {
|
|
277
297
|
return { ...parsed, error: `unrecognized argument: ${arg}` };
|
|
278
298
|
}
|
|
@@ -335,6 +355,9 @@ export function run(argv) {
|
|
|
335
355
|
effortBudgetSeams: { hostDescriptor, environ: process.env },
|
|
336
356
|
// #3286: --compact flag; DEFT_SESSION_COMPACT still resolved inside core.
|
|
337
357
|
compact: args.compact ? true : undefined,
|
|
358
|
+
steal: args.steal ? true : undefined,
|
|
359
|
+
confirm: args.confirm ? true : undefined,
|
|
360
|
+
occupant: args.occupant ?? undefined,
|
|
338
361
|
...(args.ceremonyDepthOverride !== null
|
|
339
362
|
? {
|
|
340
363
|
ceremonyDial: selectCeremonyDepth({
|
package/dist/verify-ac.d.ts
CHANGED
|
@@ -20,6 +20,8 @@ export type FindActiveXbriefResult = {
|
|
|
20
20
|
readonly paths: readonly string[];
|
|
21
21
|
readonly dir: string;
|
|
22
22
|
};
|
|
23
|
+
/** Three-state exit: pass=0, fail=1, config=2. A printed pass must not leak 201. */
|
|
24
|
+
export declare function clampVerifyAcExit(ok: boolean, code: number): number;
|
|
23
25
|
/** Run the gate and return the process exit code. */
|
|
24
26
|
export declare function run(argv: string[]): number;
|
|
25
27
|
export {};
|