@deftai/directive 0.97.0 → 0.98.1
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/cli-router/route-argv.js +6 -0
- package/dist/dispatch.d.ts +1 -1
- package/dist/dispatch.js +6 -0
- package/dist/policy.d.ts +4 -1
- package/dist/policy.js +87 -5
- package/dist/scope-record-approved-scope.d.ts +22 -0
- package/dist/scope-record-approved-scope.js +253 -0
- package/dist/triage-classify.d.ts +5 -0
- package/dist/triage-classify.js +11 -0
- package/package.json +3 -3
|
@@ -83,6 +83,9 @@ export const SUBCOMMAND_ROUTES = {
|
|
|
83
83
|
"policy:allow-direct-commits": ["policy", "allow-direct-commits"],
|
|
84
84
|
"policy:allow-bot-merge": ["policy", "allow-bot-merge"],
|
|
85
85
|
"policy:enable-value-feedback": ["policy", "enable-value-feedback"],
|
|
86
|
+
"policy:coverage-check-resume-preset": ["policy", "coverage-check-resume-preset"],
|
|
87
|
+
"policy:coverage-check-resume-dismiss": ["policy", "coverage-check-resume-dismiss"],
|
|
88
|
+
"policy:coverage-check-resume-later": ["policy", "coverage-check-resume-later"],
|
|
86
89
|
"policy:clear-value-feedback": ["policy", "clear-value-feedback"],
|
|
87
90
|
"authz:show": ["authz", "show"],
|
|
88
91
|
"authz:uat-start": ["authz", "uat-start"],
|
|
@@ -185,6 +188,9 @@ function routeNamespaceVerb(ns, verb, rest) {
|
|
|
185
188
|
return { kind: "dispatch", argv: ["scope-decompose", ...rest] };
|
|
186
189
|
if (verb === "undo")
|
|
187
190
|
return { kind: "dispatch", argv: ["scope-undo", ...rest] };
|
|
191
|
+
if (verb === "record-approved-scope") {
|
|
192
|
+
return { kind: "dispatch", argv: ["scope-record-approved-scope", ...rest] };
|
|
193
|
+
}
|
|
188
194
|
}
|
|
189
195
|
if (ns === "verify") {
|
|
190
196
|
const verifyStem = VERIFY_VERB_MAP[verb];
|
package/dist/dispatch.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ export interface DispatchIo {
|
|
|
10
10
|
writeErr: (text: string) => void;
|
|
11
11
|
}
|
|
12
12
|
/** CLI modules in packages/cli/src (excluding parity harnesses and bin/index). */
|
|
13
|
-
export declare const CLI_MODULE_VERBS: readonly ["agents-refresh", "cache", "check", "capacity-backfill", "capacity-show", "codebase-default-extractor", "codebase-map", "codebase-map-fresh", "codebase-projection-registry", "codebase-provider", "doctor", "install-upgrade", "install-uninstall", "migrate-preflight", "migrate-xbrief", "migrate-category-b", "framework-check-updates", "hook-dispatch", "umbrella-current-shape", "changelog-check", "change-init", "commit-lint", "coverage-hotspots", "policy", "authz", "escalation-cli", "pr-closing-keywords", "pr-merge-readiness", "pr-monitor", "pr-protected-issues", "pr-wait-mergeable", "pr-watch", "pr-finish-loop", "directive-finish-loop", "preflight-cache", "preflight-gh", "probe-session", "release", "release-e2e", "release-publish", "release-rollback", "scope-lifecycle", "lifecycle-event", "lifecycle-stats", "session-start", "session-ready", "plan-sequence", "slice", "subagent-monitor", "toolchain-check", "triage-actions", "triage-bootstrap", "triage-bulk", "triage-classify", "triage-help", "triage-queue", "triage-reconcile", "triage-refresh", "triage-scope", "triage-scope-drift", "triage-smoketest", "triage-subscribe", "triage-summary", "triage-welcome", "ts-check-lane", "vbrief-activate", "vbrief-build", "vbrief-preflight", "vbrief-reconcile", "vbrief-validate", "vbrief-validation", "xbrief-create", "xbrief-verify", "verify-branch", "verify-encoding", "verify-forward-coverage", "verify-test-boundary", "verify-scope-provenance", "verify-consumer-check-contract", "verify-hooks-installed", "verify-investigation", "verify-judgment-gates", "verify-no-task-runtime", "validate-links", "validate-strategy-output", "verify-biome-config", "verify-bridge-drift", "verify-capacity", "verify-contained-writes", "verify-content-manifest", "verify-skill-external-fetch-gate", "verify-contract-drift", "verify-cursor-tier1", "verify-openclaw-tier1", "verify-go-freeze", "verify-scm-boundary", "verify-session-ritual", "verify-plan-sequence", "verify-stubs", "verify-xbrief-drift", "rule-ownership-lint", "verify-story-ready", "verify-review-monitor", "verify-l4-owner", "verify-subagent-alive", "review-monitor-register", "review-monitor-release", "verify-tools", "verify-wip-cap", "verify-orphan-active", "verify-agents-md-budget", "verify-agents-md-advisory", "verify-eval-health-relocation", "verify-eval-triggers-relocation", "eval-health", "eval-run", "eval-report", "eval-triggers"];
|
|
13
|
+
export declare const CLI_MODULE_VERBS: readonly ["agents-refresh", "cache", "check", "capacity-backfill", "capacity-show", "codebase-default-extractor", "codebase-map", "codebase-map-fresh", "codebase-projection-registry", "codebase-provider", "doctor", "install-upgrade", "install-uninstall", "migrate-preflight", "migrate-xbrief", "migrate-category-b", "framework-check-updates", "hook-dispatch", "umbrella-current-shape", "changelog-check", "change-init", "commit-lint", "coverage-hotspots", "policy", "authz", "escalation-cli", "pr-closing-keywords", "pr-merge-readiness", "pr-monitor", "pr-protected-issues", "pr-wait-mergeable", "pr-watch", "pr-finish-loop", "directive-finish-loop", "preflight-cache", "preflight-gh", "probe-session", "release", "release-e2e", "release-publish", "release-rollback", "scope-lifecycle", "scope-record-approved-scope", "lifecycle-event", "lifecycle-stats", "session-start", "session-ready", "plan-sequence", "slice", "subagent-monitor", "toolchain-check", "triage-actions", "triage-bootstrap", "triage-bulk", "triage-classify", "triage-help", "triage-queue", "triage-reconcile", "triage-refresh", "triage-scope", "triage-scope-drift", "triage-smoketest", "triage-subscribe", "triage-summary", "triage-welcome", "ts-check-lane", "vbrief-activate", "vbrief-build", "vbrief-preflight", "vbrief-reconcile", "vbrief-validate", "vbrief-validation", "xbrief-create", "xbrief-verify", "verify-branch", "verify-encoding", "verify-forward-coverage", "verify-test-boundary", "verify-scope-provenance", "verify-consumer-check-contract", "verify-hooks-installed", "verify-investigation", "verify-judgment-gates", "verify-no-task-runtime", "validate-links", "validate-strategy-output", "verify-biome-config", "verify-bridge-drift", "verify-capacity", "verify-contained-writes", "verify-content-manifest", "verify-skill-external-fetch-gate", "verify-contract-drift", "verify-cursor-tier1", "verify-openclaw-tier1", "verify-go-freeze", "verify-scm-boundary", "verify-session-ritual", "verify-plan-sequence", "verify-stubs", "verify-xbrief-drift", "rule-ownership-lint", "verify-story-ready", "verify-review-monitor", "verify-l4-owner", "verify-subagent-alive", "review-monitor-register", "review-monitor-release", "verify-tools", "verify-wip-cap", "verify-orphan-active", "verify-agents-md-budget", "verify-agents-md-advisory", "verify-eval-health-relocation", "verify-eval-triggers-relocation", "eval-health", "eval-run", "eval-report", "eval-triggers"];
|
|
14
14
|
/** Core-only CLI entrypoints without a packages/cli wrapper. */
|
|
15
15
|
export declare const CORE_MODULE_VERBS: readonly ["scm", "scm-readiness", "github-auth-modes", "github-body", "issue-emit", "issue-ingest", "issue-sync-from-xbrief", "reconcile-issues", "swarm-launch", "swarm-complete-cohort", "swarm-finalize-cohort", "swarm-readiness", "swarm-routing-verify", "swarm-routing-set", "swarm-verify-review-clean", "swarm-worktrees", "framework-commands", "pack-render", "packs-slice", "prd-render", "export-spec", "project-render", "roadmap-render", "rule-map", "spec-render", "spec-validate", "code-structure-validate", "pack-migrate-skills", "pack-migrate-rules", "pack-migrate-strategies", "pack-migrate-patterns", "pack-migrate-swarm-spec", "policy-set", "setup-ghx", "scope-undo", "scope-demote", "scope-decompose", "changelog-resolve-unreleased", "architecture-preflight-sor", "feedback-file", "value-readback", "product-signal", "freshness-report"];
|
|
16
16
|
/** Colon aliases for triage-actions (mirrors cli-router SUBCOMMAND_ROUTES). */
|
package/dist/dispatch.js
CHANGED
|
@@ -68,6 +68,7 @@ export const CLI_MODULE_VERBS = [
|
|
|
68
68
|
"release-publish",
|
|
69
69
|
"release-rollback",
|
|
70
70
|
"scope-lifecycle",
|
|
71
|
+
"scope-record-approved-scope",
|
|
71
72
|
"lifecycle-event",
|
|
72
73
|
"lifecycle-stats",
|
|
73
74
|
"session-start",
|
|
@@ -211,6 +212,9 @@ export const POLICY_ACTION_ALIAS_SUBCOMMANDS = {
|
|
|
211
212
|
"policy:allow-bot-merge": "allow-bot-merge",
|
|
212
213
|
"policy:enable-value-feedback": "enable-value-feedback",
|
|
213
214
|
"policy:clear-value-feedback": "clear-value-feedback",
|
|
215
|
+
"policy:coverage-check-resume-preset": "coverage-check-resume-preset",
|
|
216
|
+
"policy:coverage-check-resume-dismiss": "coverage-check-resume-dismiss",
|
|
217
|
+
"policy:coverage-check-resume-later": "coverage-check-resume-later",
|
|
214
218
|
"policy:disable-directive": "disable-directive",
|
|
215
219
|
"policy:enable-directive": "enable-directive",
|
|
216
220
|
};
|
|
@@ -264,6 +268,7 @@ export const VERB_ALIASES = {
|
|
|
264
268
|
"verify:forward-coverage": "verify-forward-coverage",
|
|
265
269
|
"verify:test-boundary": "verify-test-boundary",
|
|
266
270
|
"verify:scope-provenance": "verify-scope-provenance",
|
|
271
|
+
"scope:record-approved-scope": "scope-record-approved-scope",
|
|
267
272
|
"verify:consumer-check-contract": "verify-consumer-check-contract",
|
|
268
273
|
"coverage:hotspots": "coverage-hotspots",
|
|
269
274
|
"verify:branch": "verify-branch",
|
|
@@ -2526,6 +2531,7 @@ const SCOPE_COMMAND_NAMES = [
|
|
|
2526
2531
|
"scope:complete",
|
|
2527
2532
|
"scope:demote",
|
|
2528
2533
|
"scope:undo",
|
|
2534
|
+
"scope:record-approved-scope",
|
|
2529
2535
|
];
|
|
2530
2536
|
/**
|
|
2531
2537
|
* Deduplicated command names for `directive commands`, preferring colon-style
|
package/dist/policy.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ interface ShowArgs {
|
|
|
7
7
|
error?: string;
|
|
8
8
|
}
|
|
9
9
|
interface SetArgs {
|
|
10
|
-
cmd: "show" | "enforce-branches" | "allow-direct-commits" | "allow-bot-merge" | "enable-value-feedback" | "clear-value-feedback" | "disable-directive" | "enable-directive" | "resolve";
|
|
10
|
+
cmd: "show" | "enforce-branches" | "allow-direct-commits" | "allow-bot-merge" | "enable-value-feedback" | "clear-value-feedback" | "coverage-check-resume-preset" | "coverage-check-resume-dismiss" | "coverage-check-resume-later" | "disable-directive" | "enable-directive" | "resolve";
|
|
11
11
|
confirm: boolean;
|
|
12
12
|
actor: string;
|
|
13
13
|
note: string;
|
|
@@ -15,6 +15,9 @@ interface SetArgs {
|
|
|
15
15
|
format: "text" | "json";
|
|
16
16
|
changedOnly: boolean;
|
|
17
17
|
field: string | null;
|
|
18
|
+
/** Strict | hatch-aware for coverage-check-resume-preset; reason for dismiss. */
|
|
19
|
+
preset?: string;
|
|
20
|
+
reason?: string;
|
|
18
21
|
error?: string;
|
|
19
22
|
}
|
|
20
23
|
/** Parse policy:show flags (mirrors _policy_show_cli.py). */
|
package/dist/policy.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import { existsSync } from "node:fs";
|
|
7
7
|
import { resolve as pathResolve, relative } from "node:path";
|
|
8
8
|
import { fileURLToPath } from "node:url";
|
|
9
|
-
import { ALLOW_BOT_MERGE_CAPABILITY_COST, clearValueFeedback, createNoDeftDirectiveFlag, describeShadowedPlanExtension, detectNoDeftDirective, detectShadowedPlanExtensions, disclosureLine, enableValueFeedback, FIELD_VALUE_FEEDBACK, FIELD_VALUE_FEEDBACK_CLI_ALIAS, formatValueFeedbackStatusLine, humanMergeDisclosureLine, inspectAllPolicies, inspectOnePolicy, loadProjectDefinition, NO_DEFT_DIRECTIVE_DISABLED_MESSAGE, NO_DEFT_DIRECTIVE_FLAG_NAME, NO_DEFT_DIRECTIVE_INCONSISTENT_MESSAGE, policyColonInvocation, projectDefinitionPath, pythonListRepr, pythonStringRepr, registeredPolicyNames, removeNoDeftDirectiveFlag, renderJson, renderText, resolveHumanMergePolicy, resolvePolicy, resolveValueFeedback, setPolicy, setRequireHumanMerge, } from "@deftai/directive-core/policy";
|
|
9
|
+
import { ALLOW_BOT_MERGE_CAPABILITY_COST, applyHatchAwareCoverageCheckResumePreset, applyLaterCoverageCheckResumeSkip, applyStrictCoverageCheckResumePreset, clearValueFeedback, createNoDeftDirectiveFlag, describeShadowedPlanExtension, detectNoDeftDirective, detectShadowedPlanExtensions, disclosureLine, dismissCoverageCheckResume, enableValueFeedback, FIELD_VALUE_FEEDBACK, FIELD_VALUE_FEEDBACK_CLI_ALIAS, formatValueFeedbackStatusLine, humanMergeDisclosureLine, inspectAllPolicies, inspectOnePolicy, loadProjectDefinition, NO_DEFT_DIRECTIVE_DISABLED_MESSAGE, NO_DEFT_DIRECTIVE_FLAG_NAME, NO_DEFT_DIRECTIVE_INCONSISTENT_MESSAGE, policyColonInvocation, projectDefinitionPath, pythonListRepr, pythonStringRepr, registeredPolicyNames, removeNoDeftDirectiveFlag, renderJson, renderText, resolveHumanMergePolicy, resolvePolicy, resolveValueFeedback, setPolicy, setRequireHumanMerge, } from "@deftai/directive-core/policy";
|
|
10
10
|
const CAPABILITY_COST_DISCLOSURE = "\u26a0 Capability-cost disclosure -- enabling direct commits to the default " +
|
|
11
11
|
"branch turns OFF the deft branch-protection policy.\n" +
|
|
12
12
|
" \u2022 Pre-commit + pre-push hooks will no longer block default-branch " +
|
|
@@ -114,7 +114,7 @@ export function parseShowArgs(argv) {
|
|
|
114
114
|
/** Parse argv for the policy CLI (show + set subcommands). */
|
|
115
115
|
export function parseArgs(argv) {
|
|
116
116
|
if (argv.length === 0) {
|
|
117
|
-
const usage = "usage: policy [show|enforce-branches|allow-direct-commits|allow-bot-merge|enable-value-feedback|clear-value-feedback|disable-directive|enable-directive|resolve] ...";
|
|
117
|
+
const usage = "usage: policy [show|enforce-branches|allow-direct-commits|allow-bot-merge|enable-value-feedback|clear-value-feedback|coverage-check-resume-preset|coverage-check-resume-dismiss|coverage-check-resume-later|disable-directive|enable-directive|resolve] ...";
|
|
118
118
|
return makeSetError(usage);
|
|
119
119
|
}
|
|
120
120
|
const cmd = argv[0];
|
|
@@ -151,6 +151,9 @@ export function parseArgs(argv) {
|
|
|
151
151
|
cmd === "allow-bot-merge" ||
|
|
152
152
|
cmd === "enable-value-feedback" ||
|
|
153
153
|
cmd === "clear-value-feedback" ||
|
|
154
|
+
cmd === "coverage-check-resume-preset" ||
|
|
155
|
+
cmd === "coverage-check-resume-dismiss" ||
|
|
156
|
+
cmd === "coverage-check-resume-later" ||
|
|
154
157
|
cmd === "disable-directive" ||
|
|
155
158
|
cmd === "enable-directive") {
|
|
156
159
|
let confirm = false;
|
|
@@ -164,16 +167,44 @@ export function parseArgs(argv) {
|
|
|
164
167
|
? policyColonInvocation("enable-value-feedback")
|
|
165
168
|
: cmd === "clear-value-feedback"
|
|
166
169
|
? policyColonInvocation("clear-value-feedback")
|
|
167
|
-
: cmd === "
|
|
168
|
-
? policyColonInvocation("
|
|
169
|
-
:
|
|
170
|
+
: cmd === "coverage-check-resume-preset"
|
|
171
|
+
? policyColonInvocation("coverage-check-resume-preset")
|
|
172
|
+
: cmd === "coverage-check-resume-dismiss"
|
|
173
|
+
? policyColonInvocation("coverage-check-resume-dismiss")
|
|
174
|
+
: cmd === "coverage-check-resume-later"
|
|
175
|
+
? policyColonInvocation("coverage-check-resume-later")
|
|
176
|
+
: cmd === "disable-directive"
|
|
177
|
+
? policyColonInvocation("disable-directive")
|
|
178
|
+
: policyColonInvocation("enable-directive");
|
|
170
179
|
let note = "";
|
|
171
180
|
let projectRoot = ".";
|
|
181
|
+
let preset = "";
|
|
182
|
+
let reason = "";
|
|
172
183
|
for (let i = 1; i < argv.length; i += 1) {
|
|
173
184
|
const arg = argv[i];
|
|
174
185
|
if (arg === "--confirm") {
|
|
175
186
|
confirm = true;
|
|
176
187
|
}
|
|
188
|
+
else if (arg === "--preset") {
|
|
189
|
+
const v = argv[i + 1];
|
|
190
|
+
if (v === undefined)
|
|
191
|
+
return makeSetError("argument --preset: expected one argument");
|
|
192
|
+
preset = v;
|
|
193
|
+
i += 1;
|
|
194
|
+
}
|
|
195
|
+
else if (arg?.startsWith("--preset=")) {
|
|
196
|
+
preset = arg.slice("--preset=".length);
|
|
197
|
+
}
|
|
198
|
+
else if (arg === "--reason") {
|
|
199
|
+
const v = argv[i + 1];
|
|
200
|
+
if (v === undefined)
|
|
201
|
+
return makeSetError("argument --reason: expected one argument");
|
|
202
|
+
reason = v;
|
|
203
|
+
i += 1;
|
|
204
|
+
}
|
|
205
|
+
else if (arg?.startsWith("--reason=")) {
|
|
206
|
+
reason = arg.slice("--reason=".length);
|
|
207
|
+
}
|
|
177
208
|
else if (arg === "--actor") {
|
|
178
209
|
const v = argv[i + 1];
|
|
179
210
|
if (v === undefined)
|
|
@@ -204,6 +235,10 @@ export function parseArgs(argv) {
|
|
|
204
235
|
else if (arg?.startsWith("--project-root=")) {
|
|
205
236
|
projectRoot = arg.slice("--project-root=".length);
|
|
206
237
|
}
|
|
238
|
+
else if (cmd === "coverage-check-resume-preset" &&
|
|
239
|
+
(arg === "strict" || arg === "hatch-aware")) {
|
|
240
|
+
preset = arg;
|
|
241
|
+
}
|
|
207
242
|
else {
|
|
208
243
|
return makeSetError(`unrecognized argument: ${arg}`);
|
|
209
244
|
}
|
|
@@ -217,6 +252,8 @@ export function parseArgs(argv) {
|
|
|
217
252
|
format: "text",
|
|
218
253
|
changedOnly: false,
|
|
219
254
|
field: null,
|
|
255
|
+
preset,
|
|
256
|
+
reason,
|
|
220
257
|
};
|
|
221
258
|
}
|
|
222
259
|
return makeSetError(`unknown subcommand: ${cmd}`);
|
|
@@ -456,6 +493,15 @@ export function run(argv) {
|
|
|
456
493
|
if (args.cmd === "clear-value-feedback") {
|
|
457
494
|
return runClearValueFeedback(args);
|
|
458
495
|
}
|
|
496
|
+
if (args.cmd === "coverage-check-resume-preset") {
|
|
497
|
+
return runCoverageCheckResumePreset(args);
|
|
498
|
+
}
|
|
499
|
+
if (args.cmd === "coverage-check-resume-dismiss") {
|
|
500
|
+
return runCoverageCheckResumeDismiss(args);
|
|
501
|
+
}
|
|
502
|
+
if (args.cmd === "coverage-check-resume-later") {
|
|
503
|
+
return runCoverageCheckResumeLater();
|
|
504
|
+
}
|
|
459
505
|
if (args.cmd === "disable-directive") {
|
|
460
506
|
return runDisableDirective(args);
|
|
461
507
|
}
|
|
@@ -464,6 +510,42 @@ export function run(argv) {
|
|
|
464
510
|
}
|
|
465
511
|
return 2;
|
|
466
512
|
}
|
|
513
|
+
/** Apply Strict or Hatch-aware coverageDebt+checkResume preset (#3189). */
|
|
514
|
+
function runCoverageCheckResumePreset(args) {
|
|
515
|
+
const root = pathResolve(args.projectRoot);
|
|
516
|
+
const preset = (args.preset ?? "").trim().toLowerCase();
|
|
517
|
+
if (preset !== "strict" && preset !== "hatch-aware") {
|
|
518
|
+
process.stdout.write("usage: policy coverage-check-resume-preset --preset strict|hatch-aware [--project-root PATH]\n" +
|
|
519
|
+
" (or: policy:coverage-check-resume-preset strict|hatch-aware)\n");
|
|
520
|
+
return 1;
|
|
521
|
+
}
|
|
522
|
+
const result = preset === "strict"
|
|
523
|
+
? applyStrictCoverageCheckResumePreset(root, { actor: args.actor, note: args.note })
|
|
524
|
+
: applyHatchAwareCoverageCheckResumePreset(root, { actor: args.actor, note: args.note });
|
|
525
|
+
process.stdout.write(result.stdout);
|
|
526
|
+
return result.exitCode;
|
|
527
|
+
}
|
|
528
|
+
/** Dismiss-with-reason for coverageDebt+checkResume (#3189). */
|
|
529
|
+
function runCoverageCheckResumeDismiss(args) {
|
|
530
|
+
const root = pathResolve(args.projectRoot);
|
|
531
|
+
const reason = (args.reason ?? "").trim();
|
|
532
|
+
if (reason.length === 0) {
|
|
533
|
+
process.stdout.write("usage: policy coverage-check-resume-dismiss --reason TEXT [--project-root PATH]\n");
|
|
534
|
+
return 1;
|
|
535
|
+
}
|
|
536
|
+
const result = dismissCoverageCheckResume(root, reason, {
|
|
537
|
+
actor: args.actor,
|
|
538
|
+
note: args.note,
|
|
539
|
+
});
|
|
540
|
+
process.stdout.write(result.stdout);
|
|
541
|
+
return result.exitCode;
|
|
542
|
+
}
|
|
543
|
+
/** Later skip — no PROJECT-DEFINITION write (#3189). */
|
|
544
|
+
function runCoverageCheckResumeLater() {
|
|
545
|
+
const result = applyLaterCoverageCheckResumeSkip();
|
|
546
|
+
process.stdout.write(result.stdout);
|
|
547
|
+
return result.exitCode;
|
|
548
|
+
}
|
|
467
549
|
if (process.argv[1] !== undefined && fileURLToPath(import.meta.url) === process.argv[1]) {
|
|
468
550
|
process.exit(run(process.argv.slice(2)));
|
|
469
551
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/** True when `candidate` is the same as or a descendant of `root` after realpath. */
|
|
3
|
+
export declare function isPathInsideRoot(root: string, candidate: string): boolean;
|
|
4
|
+
export interface ParsedArgs {
|
|
5
|
+
projectRoot: string;
|
|
6
|
+
xbriefPath: string;
|
|
7
|
+
actor: string;
|
|
8
|
+
kind: string;
|
|
9
|
+
/** Optional override for recorded xbriefRelPath (defaults: pending→active map). */
|
|
10
|
+
xbriefRelPath: string;
|
|
11
|
+
quiet: boolean;
|
|
12
|
+
error?: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Map pending/ → active/ for path-bound approval records.
|
|
16
|
+
* Returns null when the source cannot be expressed as a repo-relative path under projectRoot
|
|
17
|
+
* (absolute outside-root paths must not be stamped into xbriefRelPath — #3205 Greptile).
|
|
18
|
+
*/
|
|
19
|
+
export declare function resolveApprovalXbriefRelPath(sourceRelOrAbs: string, projectRoot: string, override?: string): string | null;
|
|
20
|
+
export declare function parseArgs(argv: string[]): ParsedArgs;
|
|
21
|
+
export declare function run(argv: string[]): number;
|
|
22
|
+
//# sourceMappingURL=scope-record-approved-scope.d.ts.map
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* CLI for scope:record-approved-scope (#3205).
|
|
4
|
+
*
|
|
5
|
+
* Deposits a human-origin approved-scope digest under
|
|
6
|
+
* `.deft/approved-scope/<plan-id>.json` so verify:scope-provenance can
|
|
7
|
+
* authorize pending→active and operator-approved expansion without same-PR
|
|
8
|
+
* self-authorization.
|
|
9
|
+
*/
|
|
10
|
+
import { existsSync, lstatSync, readFileSync, realpathSync } from "node:fs";
|
|
11
|
+
import { basename, join, relative, resolve, sep } from "node:path";
|
|
12
|
+
import { scopeProvenance } from "@deftai/directive-core";
|
|
13
|
+
import { isDirectEntrypoint } from "./entrypoint.js";
|
|
14
|
+
/** True when `candidate` is the same as or a descendant of `root` after realpath. */
|
|
15
|
+
export function isPathInsideRoot(root, candidate) {
|
|
16
|
+
let rootReal;
|
|
17
|
+
let candReal;
|
|
18
|
+
try {
|
|
19
|
+
rootReal = realpathSync(root);
|
|
20
|
+
// realpathSync fails if path does not exist; fall back to resolve for missing targets
|
|
21
|
+
candReal = existsSync(candidate) ? realpathSync(candidate) : resolve(candidate);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
const rel = relative(rootReal, candReal);
|
|
27
|
+
if (rel === "")
|
|
28
|
+
return true;
|
|
29
|
+
if (rel.startsWith(`..${sep}`) ||
|
|
30
|
+
rel === ".." ||
|
|
31
|
+
rel.startsWith("../") ||
|
|
32
|
+
rel.startsWith("..\\")) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
// Absolute relative() means different drive/root (win32)
|
|
36
|
+
if (resolve(rel) === rel && rel.includes(":"))
|
|
37
|
+
return false;
|
|
38
|
+
return !rel.startsWith("..");
|
|
39
|
+
}
|
|
40
|
+
const { buildApprovedScopeRecord, isHumanApprovalStamp, writeApprovedScopeRecord } = scopeProvenance;
|
|
41
|
+
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 with a humanApproval stamp (#3205).\n" +
|
|
45
|
+
" Agent-shaped stamps are refused. Path-binds to xbrief/active/ by default when " +
|
|
46
|
+
"the source is under pending/ or active/.");
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Map pending/ → active/ for path-bound approval records.
|
|
50
|
+
* Returns null when the source cannot be expressed as a repo-relative path under projectRoot
|
|
51
|
+
* (absolute outside-root paths must not be stamped into xbriefRelPath — #3205 Greptile).
|
|
52
|
+
*/
|
|
53
|
+
export function resolveApprovalXbriefRelPath(sourceRelOrAbs, projectRoot, override) {
|
|
54
|
+
if (override !== undefined && override.trim().length > 0) {
|
|
55
|
+
const o = override.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
56
|
+
if (o.startsWith("/") || /^[A-Za-z]:\//.test(o))
|
|
57
|
+
return null;
|
|
58
|
+
return o;
|
|
59
|
+
}
|
|
60
|
+
const root = resolve(projectRoot);
|
|
61
|
+
const abs = resolve(sourceRelOrAbs.includes(":") ||
|
|
62
|
+
sourceRelOrAbs.startsWith("/") ||
|
|
63
|
+
sourceRelOrAbs.startsWith("\\")
|
|
64
|
+
? sourceRelOrAbs
|
|
65
|
+
: join(root, sourceRelOrAbs));
|
|
66
|
+
// Lexical + realpath containment (symlink to outside root must fail closed — #3205 Greptile).
|
|
67
|
+
if (!isPathInsideRoot(root, abs)) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
let rel = relative(root, abs).replace(/\\/g, "/");
|
|
71
|
+
if (rel.length === 0 || rel.startsWith("../") || rel === "..") {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
if (existsSync(abs) && lstatSync(abs).isSymbolicLink() && !isPathInsideRoot(root, abs)) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
// Normalize pending → active for future-active binding (issue #3205 multi-PR).
|
|
83
|
+
if (rel.startsWith("xbrief/pending/")) {
|
|
84
|
+
rel = `xbrief/active/${basename(rel)}`;
|
|
85
|
+
}
|
|
86
|
+
else if (rel.startsWith("vbrief/pending/")) {
|
|
87
|
+
rel = `vbrief/active/${basename(rel)}`;
|
|
88
|
+
}
|
|
89
|
+
return rel;
|
|
90
|
+
}
|
|
91
|
+
export function parseArgs(argv) {
|
|
92
|
+
const parsed = {
|
|
93
|
+
projectRoot: ".",
|
|
94
|
+
xbriefPath: "",
|
|
95
|
+
actor: "",
|
|
96
|
+
kind: "operator",
|
|
97
|
+
xbriefRelPath: "",
|
|
98
|
+
quiet: false,
|
|
99
|
+
};
|
|
100
|
+
const positionals = [];
|
|
101
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
102
|
+
const arg = argv[i];
|
|
103
|
+
if (arg === "--help" || arg === "-h") {
|
|
104
|
+
return { ...parsed, error: usage() };
|
|
105
|
+
}
|
|
106
|
+
if (arg === "--quiet") {
|
|
107
|
+
parsed.quiet = true;
|
|
108
|
+
}
|
|
109
|
+
else if (arg === "--project-root") {
|
|
110
|
+
const value = argv[i + 1];
|
|
111
|
+
if (value === undefined) {
|
|
112
|
+
return { ...parsed, error: "argument --project-root: expected one argument" };
|
|
113
|
+
}
|
|
114
|
+
parsed.projectRoot = value;
|
|
115
|
+
i += 1;
|
|
116
|
+
}
|
|
117
|
+
else if (arg?.startsWith("--project-root=")) {
|
|
118
|
+
parsed.projectRoot = arg.slice("--project-root=".length);
|
|
119
|
+
}
|
|
120
|
+
else if (arg === "--actor") {
|
|
121
|
+
const value = argv[i + 1];
|
|
122
|
+
if (value === undefined) {
|
|
123
|
+
return { ...parsed, error: "argument --actor: expected one argument" };
|
|
124
|
+
}
|
|
125
|
+
parsed.actor = value;
|
|
126
|
+
i += 1;
|
|
127
|
+
}
|
|
128
|
+
else if (arg?.startsWith("--actor=")) {
|
|
129
|
+
parsed.actor = arg.slice("--actor=".length);
|
|
130
|
+
}
|
|
131
|
+
else if (arg === "--kind") {
|
|
132
|
+
const value = argv[i + 1];
|
|
133
|
+
if (value === undefined) {
|
|
134
|
+
return { ...parsed, error: "argument --kind: expected one argument" };
|
|
135
|
+
}
|
|
136
|
+
parsed.kind = value;
|
|
137
|
+
i += 1;
|
|
138
|
+
}
|
|
139
|
+
else if (arg?.startsWith("--kind=")) {
|
|
140
|
+
parsed.kind = arg.slice("--kind=".length);
|
|
141
|
+
}
|
|
142
|
+
else if (arg === "--xbrief-rel-path") {
|
|
143
|
+
const value = argv[i + 1];
|
|
144
|
+
if (value === undefined) {
|
|
145
|
+
return { ...parsed, error: "argument --xbrief-rel-path: expected one argument" };
|
|
146
|
+
}
|
|
147
|
+
parsed.xbriefRelPath = value;
|
|
148
|
+
i += 1;
|
|
149
|
+
}
|
|
150
|
+
else if (arg?.startsWith("--xbrief-rel-path=")) {
|
|
151
|
+
parsed.xbriefRelPath = arg.slice("--xbrief-rel-path=".length);
|
|
152
|
+
}
|
|
153
|
+
else if (arg?.startsWith("-")) {
|
|
154
|
+
return { ...parsed, error: `unrecognized argument: ${arg}` };
|
|
155
|
+
}
|
|
156
|
+
else if (arg !== undefined) {
|
|
157
|
+
positionals.push(arg);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (positionals.length === 0) {
|
|
161
|
+
return { ...parsed, error: `missing xBRIEF path\n${usage()}` };
|
|
162
|
+
}
|
|
163
|
+
if (positionals.length > 1) {
|
|
164
|
+
return { ...parsed, error: `unexpected extra args: ${positionals.slice(1).join(" ")}` };
|
|
165
|
+
}
|
|
166
|
+
parsed.xbriefPath = positionals[0] ?? "";
|
|
167
|
+
if (parsed.actor.trim().length === 0) {
|
|
168
|
+
return {
|
|
169
|
+
...parsed,
|
|
170
|
+
error: `argument --actor is required (human operator identity)\n${usage()}`,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
return parsed;
|
|
174
|
+
}
|
|
175
|
+
export function run(argv) {
|
|
176
|
+
const args = parseArgs(argv);
|
|
177
|
+
if (args.error !== undefined) {
|
|
178
|
+
process.stderr.write(`scope_record_approved_scope: ${args.error}\n`);
|
|
179
|
+
return 2;
|
|
180
|
+
}
|
|
181
|
+
const projectRoot = resolve(args.projectRoot);
|
|
182
|
+
const xbriefAbs = resolve(projectRoot, args.xbriefPath);
|
|
183
|
+
if (!existsSync(xbriefAbs)) {
|
|
184
|
+
// Also try absolute path as given
|
|
185
|
+
const alt = resolve(args.xbriefPath);
|
|
186
|
+
if (!existsSync(alt)) {
|
|
187
|
+
process.stderr.write(`scope_record_approved_scope: xBRIEF not found: ${args.xbriefPath}\n`);
|
|
188
|
+
return 2;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
const fullPath = existsSync(xbriefAbs) ? xbriefAbs : resolve(args.xbriefPath);
|
|
192
|
+
if (!isPathInsideRoot(projectRoot, fullPath)) {
|
|
193
|
+
process.stderr.write("scope_record_approved_scope: xBRIEF path escapes --project-root " +
|
|
194
|
+
"(symlink or absolute outside-root targets refused) (#3205).\n");
|
|
195
|
+
return 2;
|
|
196
|
+
}
|
|
197
|
+
let raw;
|
|
198
|
+
try {
|
|
199
|
+
raw = readFileSync(fullPath, "utf8");
|
|
200
|
+
}
|
|
201
|
+
catch (err) {
|
|
202
|
+
process.stderr.write(`scope_record_approved_scope: failed to read xBRIEF: ${String(err)}\n`);
|
|
203
|
+
return 2;
|
|
204
|
+
}
|
|
205
|
+
let payload;
|
|
206
|
+
try {
|
|
207
|
+
payload = JSON.parse(raw);
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
process.stderr.write("scope_record_approved_scope: xBRIEF is not valid JSON\n");
|
|
211
|
+
return 2;
|
|
212
|
+
}
|
|
213
|
+
const stamp = {
|
|
214
|
+
kind: args.kind.trim(),
|
|
215
|
+
actor: args.actor.trim(),
|
|
216
|
+
mintedAt: new Date().toISOString().replace(/\.\d{3}Z$/, "Z"),
|
|
217
|
+
mintedVia: "scope:record-approved-scope",
|
|
218
|
+
};
|
|
219
|
+
if (!isHumanApprovalStamp(stamp)) {
|
|
220
|
+
process.stderr.write("scope_record_approved_scope: refused agent/non-human stamp " +
|
|
221
|
+
`(kind=${stamp.kind}, actor=${stamp.actor}). Use a human operator actor ` +
|
|
222
|
+
"and kind in {operator,human,user,cli,github-user,interactive,renewed-approval} (#3205).\n");
|
|
223
|
+
return 1;
|
|
224
|
+
}
|
|
225
|
+
const xbriefRelPath = resolveApprovalXbriefRelPath(fullPath, projectRoot, args.xbriefRelPath.length > 0 ? args.xbriefRelPath : undefined);
|
|
226
|
+
if (xbriefRelPath === null) {
|
|
227
|
+
process.stderr.write("scope_record_approved_scope: xBRIEF path must resolve under --project-root " +
|
|
228
|
+
"(repo-relative path required for approval binding) (#3205).\n");
|
|
229
|
+
return 2;
|
|
230
|
+
}
|
|
231
|
+
const record = buildApprovedScopeRecord({
|
|
232
|
+
xbriefRelPath,
|
|
233
|
+
payload,
|
|
234
|
+
humanApproval: stamp,
|
|
235
|
+
xbriefRawText: raw,
|
|
236
|
+
});
|
|
237
|
+
const outPath = writeApprovedScopeRecord(projectRoot, record);
|
|
238
|
+
if (!args.quiet) {
|
|
239
|
+
process.stdout.write(`scope_record_approved_scope: wrote ${outPath}\n` +
|
|
240
|
+
` planId: ${record.planId}\n` +
|
|
241
|
+
` xbriefRelPath: ${record.xbriefRelPath}\n` +
|
|
242
|
+
` fileScopeDigest: ${record.fileScopeDigest}\n` +
|
|
243
|
+
` paths: ${record.fileScope.length}\n` +
|
|
244
|
+
` humanApproval: ${stamp.kind}/${stamp.actor}\n` +
|
|
245
|
+
" Next: commit this file on the merge base (or a prior PR) before " +
|
|
246
|
+
"activation/expansion in the implementation change set (#3205).\n");
|
|
247
|
+
}
|
|
248
|
+
return 0;
|
|
249
|
+
}
|
|
250
|
+
if (isDirectEntrypoint(import.meta.url)) {
|
|
251
|
+
process.exit(run(process.argv.slice(2)));
|
|
252
|
+
}
|
|
253
|
+
//# sourceMappingURL=scope-record-approved-scope.js.map
|
|
@@ -12,6 +12,11 @@ export interface ParsedArgs {
|
|
|
12
12
|
allowCrossRepo: boolean;
|
|
13
13
|
/** Opt-in: include closed issues (default open-only, #3125). */
|
|
14
14
|
includeClosed: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Opt-in re-enrich: re-plan already-stamped issues with additive label deltas
|
|
17
|
+
* (#3197). Requires --mirror; dry-run by default until --apply.
|
|
18
|
+
*/
|
|
19
|
+
reEnrich: boolean;
|
|
15
20
|
/** Raw --author value (LOGIN, @me, comma allow-list); null = no filter (#3129). */
|
|
16
21
|
author: string | null;
|
|
17
22
|
/** Apply batch size (rate-limit awareness). */
|
package/dist/triage-classify.js
CHANGED
|
@@ -36,6 +36,7 @@ export function parseArgs(argv) {
|
|
|
36
36
|
repo: null,
|
|
37
37
|
allowCrossRepo: false,
|
|
38
38
|
includeClosed: false,
|
|
39
|
+
reEnrich: false,
|
|
39
40
|
author: null,
|
|
40
41
|
batchSize: null,
|
|
41
42
|
delayMs: null,
|
|
@@ -64,6 +65,9 @@ export function parseArgs(argv) {
|
|
|
64
65
|
else if (arg === "--include-closed") {
|
|
65
66
|
parsed.includeClosed = true;
|
|
66
67
|
}
|
|
68
|
+
else if (arg === "--re-enrich") {
|
|
69
|
+
parsed.reEnrich = true;
|
|
70
|
+
}
|
|
67
71
|
else if (arg === "--author-mine") {
|
|
68
72
|
parsed.author = "@me";
|
|
69
73
|
}
|
|
@@ -185,6 +189,12 @@ export function parseArgs(argv) {
|
|
|
185
189
|
error: "--apply requires --mirror (Tier-1 label mirror / bootstrap mass-triage, #1423)",
|
|
186
190
|
};
|
|
187
191
|
}
|
|
192
|
+
if (parsed.reEnrich && !parsed.doMirror) {
|
|
193
|
+
return {
|
|
194
|
+
...parsed,
|
|
195
|
+
error: "--re-enrich requires --mirror (additive re-plan of already-stamped issues, #3197)",
|
|
196
|
+
};
|
|
197
|
+
}
|
|
188
198
|
if ((parsed.includeClosed ||
|
|
189
199
|
parsed.author !== null ||
|
|
190
200
|
parsed.batchSize !== null ||
|
|
@@ -244,6 +254,7 @@ export function run(argv, options = {}) {
|
|
|
244
254
|
repo: args.repo,
|
|
245
255
|
allowCrossRepo: args.allowCrossRepo,
|
|
246
256
|
includeClosed: args.includeClosed,
|
|
257
|
+
reEnrich: args.reEnrich,
|
|
247
258
|
...(authorFilter !== null && authorFilter !== undefined ? { authorFilter } : {}),
|
|
248
259
|
...(args.batchSize !== null ? { batchSize: args.batchSize } : {}),
|
|
249
260
|
...(args.delayMs !== null ? { delayMs: args.delayMs } : {}),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deftai/directive",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.98.1",
|
|
4
4
|
"description": "Directive CLI — npm install path for the Deft Directive framework.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://deftai.github.io/directive/",
|
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
"provenance": true
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@deftai/directive-core": "^0.
|
|
38
|
-
"@deftai/directive-content": "^0.
|
|
37
|
+
"@deftai/directive-core": "^0.98.1",
|
|
38
|
+
"@deftai/directive-content": "^0.98.1"
|
|
39
39
|
},
|
|
40
40
|
"scripts": {
|
|
41
41
|
"build": "tsc -b"
|