@deftai/directive 0.99.0 → 0.101.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/agents-refresh.js +8 -1
- package/dist/authz.js +100 -5
- package/dist/dispatch.d.ts +1 -1
- package/dist/dispatch.js +8 -0
- package/dist/session-start.d.ts +14 -0
- package/dist/session-start.js +79 -1
- package/dist/triage-classify.d.ts +5 -0
- package/dist/triage-classify.js +47 -1
- package/dist/verify-ac.d.ts +26 -0
- package/dist/verify-ac.js +244 -0
- package/dist/verify-completed-tracked.d.ts +15 -0
- package/dist/verify-completed-tracked.js +88 -0
- package/dist/verify-literal-ac.d.ts +14 -0
- package/dist/verify-literal-ac.js +161 -0
- package/dist/verify-story-ready.js +1 -0
- package/package.json +3 -3
package/dist/agents-refresh.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* agents:refresh — rewrite AGENTS.md managed section from the canonical template (#768 / #1996).
|
|
4
|
+
* #3286: when the managed section is already current, print one-line
|
|
5
|
+
* "unchanged - sha match" (content-based; always plans against the live AGENTS.md).
|
|
4
6
|
*/
|
|
5
7
|
import { resolve } from "node:path";
|
|
6
8
|
import { fileURLToPath } from "node:url";
|
|
7
9
|
import { applyAgentsRefresh } from "@deftai/directive-core/platform";
|
|
10
|
+
import { formatDepositShaMatchLine } from "@deftai/directive-core/session";
|
|
8
11
|
export function parseAgentsRefreshArgs(argv) {
|
|
9
12
|
let projectRoot = process.cwd();
|
|
10
13
|
let check = false;
|
|
@@ -40,6 +43,9 @@ export function runAgentsRefresh(argv) {
|
|
|
40
43
|
process.stderr.write(`agents:refresh: ${args.error}\n`);
|
|
41
44
|
return 2;
|
|
42
45
|
}
|
|
46
|
+
// Always plan against live AGENTS.md (#3286 Greptile P1: deposit fingerprint
|
|
47
|
+
// alone cannot detect local managed-section edits). Content-current → one-line
|
|
48
|
+
// sha-match phrasing for harness parsers.
|
|
43
49
|
// The read->compute->write is serialized behind an advisory lock and written
|
|
44
50
|
// atomically inside applyAgentsRefresh, so concurrent refreshers cannot clobber
|
|
45
51
|
// one another's session= write or observe a partial write (#1329).
|
|
@@ -54,7 +60,8 @@ export function runAgentsRefresh(argv) {
|
|
|
54
60
|
return 1;
|
|
55
61
|
}
|
|
56
62
|
if (state === "current") {
|
|
57
|
-
|
|
63
|
+
// #3286: one-line no-op phrasing when managed section already matches template.
|
|
64
|
+
process.stdout.write(`${formatDepositShaMatchLine("agents:refresh")}\n`);
|
|
58
65
|
return 0;
|
|
59
66
|
}
|
|
60
67
|
if (state === "template-missing" || state === "template-malformed" || state === "unreadable") {
|
package/dist/authz.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* Authz CLI (#2944 Wave 1 + #1095 Wave 4 + #3110): human-origin grants + UAT lease +
|
|
4
|
-
* AFK closed-verb templates
|
|
3
|
+
* Authz CLI (#2944 Wave 1 + #1095 Wave 4 + #3110 + #3291): human-origin grants + UAT lease +
|
|
4
|
+
* AFK closed-verb templates + structural scope:decompose apply grants
|
|
5
|
+
* (mint via mintHumanOriginGrant / mintDecomposeStructuralApplyGrant only).
|
|
5
6
|
*
|
|
6
7
|
* deft authz:show
|
|
7
8
|
* deft authz:uat-start -- --campaign <id> [--actor <name>] [--note <text>] [--confirm]
|
|
@@ -9,6 +10,7 @@
|
|
|
9
10
|
* deft authz:grant -- --operations edit,push --surfaces 'src/**' --cohort <id> ... [--confirm]
|
|
10
11
|
* deft authz:grant -- --template release-publish --target 0.30.0 [--confirm]
|
|
11
12
|
* deft authz:grant -- --template finish-loop [--confirm]
|
|
13
|
+
* deft authz:grant -- --parent <parent.xbrief.json> --draft <draft.json> [--repo owner/name] [--confirm]
|
|
12
14
|
* deft authz:revoke -- <grant-id> [--confirm]
|
|
13
15
|
*
|
|
14
16
|
* **UAT-active hard refuse (#3110):** while any UAT lease is active, ALL mutating
|
|
@@ -20,8 +22,9 @@
|
|
|
20
22
|
* TTY + controlling terminal + `--confirm` + typed phrase `mint`; agent/CI env
|
|
21
23
|
* markers refuse fail-closed. Argv `--confirm` alone is never enough.
|
|
22
24
|
*/
|
|
23
|
-
import { closeSync, openSync, readSync } from "node:fs";
|
|
24
|
-
import {
|
|
25
|
+
import { closeSync, existsSync, openSync, readSync } from "node:fs";
|
|
26
|
+
import { isAbsolute, resolve } from "node:path";
|
|
27
|
+
import { AFK_TEMPLATE_NAMES, AUTHZ_OPERATIONS, CLOSED_VERB_TEMPLATE_NAMES, FINISH_LOOP_TEMPLATE_NAME, formatDecomposeStructuralMintCommand, isAfkTemplateName, isClosedVerbTemplateName, isFinishLoopTemplateName, loadAuthzState, mintAfkTemplateGrant, mintDecomposeStructuralApplyGrant, mintHumanOriginGrant, revokeGrant, showAuthzSnapshot, startUatLease, suspendUatLease, toProjectRelativePosix, } from "@deftai/directive-core/authz";
|
|
25
28
|
/**
|
|
26
29
|
* Env markers that indicate an agent/host/CI shell even when stdin reports a TTY
|
|
27
30
|
* (pseudo-terminal residual; #3110 Greptile). Presence refuses mutating authz.
|
|
@@ -104,6 +107,8 @@ function parseArgv(argv) {
|
|
|
104
107
|
grantId: null,
|
|
105
108
|
template: null,
|
|
106
109
|
target: null,
|
|
110
|
+
parent: null,
|
|
111
|
+
draft: null,
|
|
107
112
|
format: "text",
|
|
108
113
|
confirm: false,
|
|
109
114
|
};
|
|
@@ -224,6 +229,14 @@ function parseArgv(argv) {
|
|
|
224
229
|
base.target = args[++i] ?? null;
|
|
225
230
|
continue;
|
|
226
231
|
}
|
|
232
|
+
if (a === "--parent") {
|
|
233
|
+
base.parent = args[++i] ?? null;
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
if (a === "--draft") {
|
|
237
|
+
base.draft = args[++i] ?? null;
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
227
240
|
if (a === "--confirm") {
|
|
228
241
|
base.confirm = true;
|
|
229
242
|
continue;
|
|
@@ -248,6 +261,8 @@ function helpText() {
|
|
|
248
261
|
" [--stories 2944] [--plan-ref <id>] [--repo owner/name] [--branch <b>] [--expires ISO] [--confirm]",
|
|
249
262
|
" deft authz:grant -- --template release-publish --target 0.30.0 [--actor <name>] [--expires ISO] [--confirm]",
|
|
250
263
|
" deft authz:grant -- --template finish-loop [--actor <name>] [--expires ISO] [--confirm]",
|
|
264
|
+
" deft authz:grant -- --parent <parent.xbrief.json> --draft <draft.json> \\",
|
|
265
|
+
" [--repo owner/name] [--expires ISO] [--single-use] [--actor <name>] [--confirm]",
|
|
251
266
|
" deft authz:revoke -- <grant-id> [--confirm]",
|
|
252
267
|
"",
|
|
253
268
|
"Human-origin grants are minted only via this CLI (origin.kind=operator-cli).",
|
|
@@ -263,6 +278,16 @@ function helpText() {
|
|
|
263
278
|
" End UAT only after the lease is cleared out-of-band (state edit / suspend path",
|
|
264
279
|
" that does not run while the lease is active) — or suspend before re-minting.",
|
|
265
280
|
"",
|
|
281
|
+
"Structural scope:decompose apply (#3239 / #3291):",
|
|
282
|
+
" Pass --parent + --draft (paths under project root). Digest is SHA-256 of exact",
|
|
283
|
+
" draft bytes; binds parent, target, worktree, and optional --repo. Mints only",
|
|
284
|
+
" scope.decompose.apply.structural via mintDecomposeStructuralApplyGrant.",
|
|
285
|
+
" Example (after --check validates the draft):",
|
|
286
|
+
" deft authz:grant -- --parent xbrief/pending/epic.xbrief.json \\",
|
|
287
|
+
" --draft xbrief/.triage-cache/decompositions/draft.json --confirm",
|
|
288
|
+
" Then: deft scope:decompose -- <parent> --draft <draft>",
|
|
289
|
+
" scope:decompose --check remains ungated (no structural grant required).",
|
|
290
|
+
"",
|
|
266
291
|
`AFK templates (#1095 / #871): ${AFK_TEMPLATE_NAMES.join(", ")}`,
|
|
267
292
|
` Closed-verb (#1095): ${CLOSED_VERB_TEMPLATE_NAMES.join(", ")} — require --target`,
|
|
268
293
|
` Finish-loop (#871): ${FINISH_LOOP_TEMPLATE_NAME} — edit/push/pr/merge (no release ops)`,
|
|
@@ -487,6 +512,75 @@ export function main(argv = process.argv.slice(2), seams = {}) {
|
|
|
487
512
|
return 0;
|
|
488
513
|
}
|
|
489
514
|
case "grant": {
|
|
515
|
+
// Structural decompose apply path (#3291): --parent + --draft → mintDecomposeStructuralApplyGrant.
|
|
516
|
+
const parentRaw = args.parent?.trim() ?? "";
|
|
517
|
+
const draftRaw = args.draft?.trim() ?? "";
|
|
518
|
+
const hasParent = parentRaw.length > 0;
|
|
519
|
+
const hasDraft = draftRaw.length > 0;
|
|
520
|
+
if (hasParent !== hasDraft) {
|
|
521
|
+
process.stderr.write("authz:grant structural mint requires both --parent <path> and --draft <path>\n" +
|
|
522
|
+
` Example: ${formatDecomposeStructuralMintCommand(hasParent ? parentRaw : "xbrief/pending/parent.xbrief.json", hasDraft ? draftRaw : "xbrief/.triage-cache/decompositions/draft.json", { repo: args.repo })}\n`);
|
|
523
|
+
return 2;
|
|
524
|
+
}
|
|
525
|
+
if (hasParent && hasDraft) {
|
|
526
|
+
if (args.template !== null && args.template.trim().length > 0) {
|
|
527
|
+
process.stderr.write("authz:grant: --parent/--draft structural mint cannot be combined with --template\n");
|
|
528
|
+
return 2;
|
|
529
|
+
}
|
|
530
|
+
if (args.operations.length > 0) {
|
|
531
|
+
process.stderr.write("authz:grant: --parent/--draft structural mint cannot be combined with --operations " +
|
|
532
|
+
"(operation is fixed to scope.decompose.apply.structural)\n");
|
|
533
|
+
return 2;
|
|
534
|
+
}
|
|
535
|
+
const root = resolve(args.projectRoot);
|
|
536
|
+
const parentAbs = isAbsolute(parentRaw) ? resolve(parentRaw) : resolve(root, parentRaw);
|
|
537
|
+
const draftAbs = isAbsolute(draftRaw) ? resolve(draftRaw) : resolve(root, draftRaw);
|
|
538
|
+
const parentRel = toProjectRelativePosix(root, parentAbs);
|
|
539
|
+
const draftRel = toProjectRelativePosix(root, draftAbs);
|
|
540
|
+
if (parentRel === null) {
|
|
541
|
+
process.stderr.write(`authz:grant: --parent path is outside project root: ${parentRaw}\n`);
|
|
542
|
+
return 2;
|
|
543
|
+
}
|
|
544
|
+
if (draftRel === null) {
|
|
545
|
+
process.stderr.write(`authz:grant: --draft path is outside project root: ${draftRaw}\n`);
|
|
546
|
+
return 2;
|
|
547
|
+
}
|
|
548
|
+
if (!existsSync(parentAbs)) {
|
|
549
|
+
process.stderr.write(`authz:grant: --parent path does not exist: ${parentRaw}\n`);
|
|
550
|
+
return 2;
|
|
551
|
+
}
|
|
552
|
+
if (!existsSync(draftAbs)) {
|
|
553
|
+
process.stderr.write(`authz:grant: --draft path does not exist: ${draftRaw}\n`);
|
|
554
|
+
return 2;
|
|
555
|
+
}
|
|
556
|
+
const blocked = gateConfirm();
|
|
557
|
+
if (blocked !== null)
|
|
558
|
+
return blocked;
|
|
559
|
+
const grant = mintDecomposeStructuralApplyGrant({
|
|
560
|
+
projectRoot: root,
|
|
561
|
+
parentPath: parentAbs,
|
|
562
|
+
draftPath: draftAbs,
|
|
563
|
+
actor: args.actor,
|
|
564
|
+
repo: args.repo,
|
|
565
|
+
expiresAt: args.expiresAt,
|
|
566
|
+
singleUse: args.singleUse,
|
|
567
|
+
});
|
|
568
|
+
// mintDecomposeStructuralApplyGrant always sets contentDigest + worktree.
|
|
569
|
+
const digest = grant.scope.contentDigest || "";
|
|
570
|
+
process.stdout.write(`✓ human-origin structural grant minted id=${grant.id} origin=${grant.origin.kind}\n`);
|
|
571
|
+
process.stdout.write(` ops=[${grant.scope.operations.join(",")}] digest=${digest.slice(0, 12)}…\n`);
|
|
572
|
+
process.stdout.write(` parent=${parentRel} draft=${draftRel}\n`);
|
|
573
|
+
if (grant.scope.repo) {
|
|
574
|
+
process.stdout.write(` repo=${grant.scope.repo}\n`);
|
|
575
|
+
}
|
|
576
|
+
process.stdout.write(` worktree=${grant.scope.worktree || root}\n`);
|
|
577
|
+
if (grant.semantics.singleUse) {
|
|
578
|
+
process.stdout.write(" single-use=true (spent on first successful apply)\n");
|
|
579
|
+
}
|
|
580
|
+
process.stdout.write(" Apply: deft scope:decompose -- <parent> --draft <draft>\n" +
|
|
581
|
+
" Authorization SoT: Wave 1 grant store (.deft/authz/grants) — not session-auth.\n");
|
|
582
|
+
return 0;
|
|
583
|
+
}
|
|
490
584
|
// AFK template path (#1095 / #871): presets only — still mintHumanOriginGrant.
|
|
491
585
|
if (args.template !== null && args.template.trim().length > 0) {
|
|
492
586
|
if (!isAfkTemplateName(args.template)) {
|
|
@@ -529,7 +623,8 @@ export function main(argv = process.argv.slice(2), seams = {}) {
|
|
|
529
623
|
return 0;
|
|
530
624
|
}
|
|
531
625
|
if (args.operations.length === 0) {
|
|
532
|
-
process.stderr.write("authz:grant requires --operations <edit,push
|
|
626
|
+
process.stderr.write("authz:grant requires --operations <edit,push,...>, --template <finish-loop|release-*>, " +
|
|
627
|
+
"or --parent + --draft (structural scope:decompose apply)\n");
|
|
533
628
|
return 2;
|
|
534
629
|
}
|
|
535
630
|
{
|
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", "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"];
|
|
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-completed-tracked", "verify-literal-ac", "verify-ac", "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-pre-dispatch", "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", "decision-write", "decision-list"];
|
|
16
16
|
/** Colon aliases for triage-actions (mirrors cli-router SUBCOMMAND_ROUTES). */
|
package/dist/dispatch.js
CHANGED
|
@@ -137,6 +137,9 @@ export const CLI_MODULE_VERBS = [
|
|
|
137
137
|
"verify-tools",
|
|
138
138
|
"verify-wip-cap",
|
|
139
139
|
"verify-orphan-active",
|
|
140
|
+
"verify-completed-tracked",
|
|
141
|
+
"verify-literal-ac",
|
|
142
|
+
"verify-ac",
|
|
140
143
|
"verify-agents-md-budget",
|
|
141
144
|
"verify-agents-md-advisory",
|
|
142
145
|
"verify-eval-health-relocation",
|
|
@@ -279,6 +282,11 @@ export const VERB_ALIASES = {
|
|
|
279
282
|
"verify:vbrief-conformance": "vbrief-validate",
|
|
280
283
|
"verify:wip-cap": "verify-wip-cap",
|
|
281
284
|
"verify:orphan-active": "verify-orphan-active",
|
|
285
|
+
"verify:completed-tracked": "verify-completed-tracked",
|
|
286
|
+
"verify:literal-ac": "verify-literal-ac",
|
|
287
|
+
"verify:literal-acceptance": "verify-literal-ac",
|
|
288
|
+
"verify:ac": "verify-ac",
|
|
289
|
+
"verify:acceptance": "verify-ac",
|
|
282
290
|
"verify:agents-md-budget": "verify-agents-md-budget",
|
|
283
291
|
"verify:agents-md-advisory": "verify-agents-md-advisory",
|
|
284
292
|
"verify:eval-health-relocation": "verify-eval-health-relocation",
|
package/dist/session-start.d.ts
CHANGED
|
@@ -18,6 +18,20 @@ export interface ParsedSessionStartArgs {
|
|
|
18
18
|
* Applied as resolve inputs via a one-shot config override in run().
|
|
19
19
|
*/
|
|
20
20
|
ceremonyDepthOverride: CeremonyDepth | null;
|
|
21
|
+
/**
|
|
22
|
+
* #3266: host/CLI effort-budget cap fields for bank-the-pass guidance.
|
|
23
|
+
* Merged with DEFT_HOST_EFFORT_BUDGET JSON when present.
|
|
24
|
+
*/
|
|
25
|
+
effortBudgetHost: {
|
|
26
|
+
maxTurns?: number;
|
|
27
|
+
maxBudget?: number;
|
|
28
|
+
hardBudget?: boolean;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* #3286: compact orientation output (terse machine format). Verbose remains
|
|
32
|
+
* the default; also settable via DEFT_SESSION_COMPACT=1.
|
|
33
|
+
*/
|
|
34
|
+
compact: boolean;
|
|
21
35
|
error?: string;
|
|
22
36
|
}
|
|
23
37
|
/** Parse session:start CLI args, mirroring scripts/session_start.py. */
|
package/dist/session-start.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { CEREMONY_DEPTHS, normalizeCeremonyModelTier, normalizeCeremonyProjectShape, normalizeCeremonyTaskSize, selectCeremonyDepth, } from "@deftai/directive-core/policy";
|
|
5
|
-
import { COLD_CEREMONY_TIER, parseDeferrals, READ_ONLY_POSTURE, REARM_CEREMONY_TIER, ritualStatePath, runSessionStart, SESSION_CEREMONY_TIERS, } from "@deftai/directive-core/session";
|
|
5
|
+
import { COLD_CEREMONY_TIER, parseDeferrals, READ_ONLY_POSTURE, REARM_CEREMONY_TIER, resolveProductionHostEffortDescriptor, ritualStatePath, runSessionStart, SESSION_CEREMONY_TIERS, } from "@deftai/directive-core/session";
|
|
6
6
|
function parseCeremonyDepth(raw) {
|
|
7
7
|
const value = raw.trim().toLowerCase();
|
|
8
8
|
if (CEREMONY_DEPTHS.includes(value)) {
|
|
@@ -29,6 +29,8 @@ export function parseArgs(argv) {
|
|
|
29
29
|
ceremonyTier: COLD_CEREMONY_TIER,
|
|
30
30
|
ceremonyDialInputs: {},
|
|
31
31
|
ceremonyDepthOverride: null,
|
|
32
|
+
effortBudgetHost: {},
|
|
33
|
+
compact: false,
|
|
32
34
|
};
|
|
33
35
|
const dialInputs = {};
|
|
34
36
|
for (let i = 0; i < argv.length; i += 1) {
|
|
@@ -211,6 +213,66 @@ export function parseArgs(argv) {
|
|
|
211
213
|
}
|
|
212
214
|
parsed.ceremonyDepthOverride = depth;
|
|
213
215
|
}
|
|
216
|
+
else if (arg === "--max-turns") {
|
|
217
|
+
// #3266: harness hard turn budget for bank-the-pass guidance
|
|
218
|
+
const value = argv[i + 1];
|
|
219
|
+
if (value === undefined) {
|
|
220
|
+
return { ...parsed, error: "argument --max-turns: expected one argument" };
|
|
221
|
+
}
|
|
222
|
+
const n = Number(value);
|
|
223
|
+
if (!Number.isFinite(n) || n < 0) {
|
|
224
|
+
return {
|
|
225
|
+
...parsed,
|
|
226
|
+
error: `argument --max-turns: expected non-negative number, got ${JSON.stringify(value)}`,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
parsed.effortBudgetHost.maxTurns = n;
|
|
230
|
+
i += 1;
|
|
231
|
+
}
|
|
232
|
+
else if (arg?.startsWith("--max-turns=")) {
|
|
233
|
+
const value = arg.slice("--max-turns=".length);
|
|
234
|
+
const n = Number(value);
|
|
235
|
+
if (!Number.isFinite(n) || n < 0) {
|
|
236
|
+
return {
|
|
237
|
+
...parsed,
|
|
238
|
+
error: `argument --max-turns: expected non-negative number, got ${JSON.stringify(value)}`,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
parsed.effortBudgetHost.maxTurns = n;
|
|
242
|
+
}
|
|
243
|
+
else if (arg === "--max-budget") {
|
|
244
|
+
const value = argv[i + 1];
|
|
245
|
+
if (value === undefined) {
|
|
246
|
+
return { ...parsed, error: "argument --max-budget: expected one argument" };
|
|
247
|
+
}
|
|
248
|
+
const n = Number(value);
|
|
249
|
+
if (!Number.isFinite(n) || n < 0) {
|
|
250
|
+
return {
|
|
251
|
+
...parsed,
|
|
252
|
+
error: `argument --max-budget: expected non-negative number, got ${JSON.stringify(value)}`,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
parsed.effortBudgetHost.maxBudget = n;
|
|
256
|
+
i += 1;
|
|
257
|
+
}
|
|
258
|
+
else if (arg?.startsWith("--max-budget=")) {
|
|
259
|
+
const value = arg.slice("--max-budget=".length);
|
|
260
|
+
const n = Number(value);
|
|
261
|
+
if (!Number.isFinite(n) || n < 0) {
|
|
262
|
+
return {
|
|
263
|
+
...parsed,
|
|
264
|
+
error: `argument --max-budget: expected non-negative number, got ${JSON.stringify(value)}`,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
parsed.effortBudgetHost.maxBudget = n;
|
|
268
|
+
}
|
|
269
|
+
else if (arg === "--hard-budget") {
|
|
270
|
+
parsed.effortBudgetHost.hardBudget = true;
|
|
271
|
+
}
|
|
272
|
+
else if (arg === "--compact") {
|
|
273
|
+
// #3286: terse machine orientation output (opt-in; verbose remains default)
|
|
274
|
+
parsed.compact = true;
|
|
275
|
+
}
|
|
214
276
|
else {
|
|
215
277
|
return { ...parsed, error: `unrecognized argument: ${arg}` };
|
|
216
278
|
}
|
|
@@ -247,6 +309,19 @@ export function run(argv) {
|
|
|
247
309
|
}
|
|
248
310
|
return true;
|
|
249
311
|
});
|
|
312
|
+
// #3266: production host adapter — native harness env + DEFT_HOST_EFFORT_BUDGET + CLI flags.
|
|
313
|
+
const hostDescriptor = {
|
|
314
|
+
...resolveProductionHostEffortDescriptor(process.env),
|
|
315
|
+
};
|
|
316
|
+
if (args.effortBudgetHost.maxTurns !== undefined) {
|
|
317
|
+
hostDescriptor.maxTurns = args.effortBudgetHost.maxTurns;
|
|
318
|
+
}
|
|
319
|
+
if (args.effortBudgetHost.maxBudget !== undefined) {
|
|
320
|
+
hostDescriptor.maxBudget = args.effortBudgetHost.maxBudget;
|
|
321
|
+
}
|
|
322
|
+
if (args.effortBudgetHost.hardBudget === true) {
|
|
323
|
+
hostDescriptor.hardBudget = true;
|
|
324
|
+
}
|
|
250
325
|
let result;
|
|
251
326
|
try {
|
|
252
327
|
// #3214: --ceremony-depth forces this session only (not persisted to policy).
|
|
@@ -257,6 +332,9 @@ export function run(argv) {
|
|
|
257
332
|
allowOptionalNetwork: args.withNetwork ? true : undefined,
|
|
258
333
|
ceremonyTier: args.ceremonyTier,
|
|
259
334
|
ceremonyDialInputs: args.ceremonyDialInputs,
|
|
335
|
+
effortBudgetSeams: { hostDescriptor, environ: process.env },
|
|
336
|
+
// #3286: --compact flag; DEFT_SESSION_COMPACT still resolved inside core.
|
|
337
|
+
compact: args.compact ? true : undefined,
|
|
260
338
|
...(args.ceremonyDepthOverride !== null
|
|
261
339
|
? {
|
|
262
340
|
ceremonyDial: selectCeremonyDepth({
|
|
@@ -17,6 +17,11 @@ export interface ParsedArgs {
|
|
|
17
17
|
* (#3197). Requires --mirror; dry-run by default until --apply.
|
|
18
18
|
*/
|
|
19
19
|
reEnrich: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Dismiss SCM label-mirror discovery tip without running a dry-run (#3124).
|
|
22
|
+
* Standalone production entry for recordMirrorDiscoveryAcked.
|
|
23
|
+
*/
|
|
24
|
+
ackDiscovery: boolean;
|
|
20
25
|
/** Raw --author value (LOGIN, @me, comma allow-list); null = no filter (#3129). */
|
|
21
26
|
author: string | null;
|
|
22
27
|
/** Apply batch size (rate-limit awareness). */
|
package/dist/triage-classify.js
CHANGED
|
@@ -3,7 +3,7 @@ import { statSync } from "node:fs";
|
|
|
3
3
|
import { resolve } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { resolveAuthorFilter, } from "@deftai/directive-core/dist/triage/author-filter.js";
|
|
6
|
-
import { labelMirrorOutcomeToJson, listProject, mirrorLabels, renderLabelMirrorReport, validateProject, } from "@deftai/directive-core/dist/triage/classify/index.js";
|
|
6
|
+
import { labelMirrorOutcomeToJson, listProject, mirrorDiscoveryStateExists, mirrorLabels, recordMirrorDiscoveryAcked, recordMirrorDiscoverySuccessfulDryRun, renderLabelMirrorReport, validateProject, } from "@deftai/directive-core/dist/triage/classify/index.js";
|
|
7
7
|
function parseNonNegInt(raw, flag) {
|
|
8
8
|
const n = Number.parseInt(raw, 10);
|
|
9
9
|
if (!Number.isFinite(n) || String(n) !== raw.trim() || n < 0) {
|
|
@@ -37,6 +37,7 @@ export function parseArgs(argv) {
|
|
|
37
37
|
allowCrossRepo: false,
|
|
38
38
|
includeClosed: false,
|
|
39
39
|
reEnrich: false,
|
|
40
|
+
ackDiscovery: false,
|
|
40
41
|
author: null,
|
|
41
42
|
batchSize: null,
|
|
42
43
|
delayMs: null,
|
|
@@ -68,6 +69,9 @@ export function parseArgs(argv) {
|
|
|
68
69
|
else if (arg === "--re-enrich") {
|
|
69
70
|
parsed.reEnrich = true;
|
|
70
71
|
}
|
|
72
|
+
else if (arg === "--ack-discovery") {
|
|
73
|
+
parsed.ackDiscovery = true;
|
|
74
|
+
}
|
|
71
75
|
else if (arg === "--author-mine") {
|
|
72
76
|
parsed.author = "@me";
|
|
73
77
|
}
|
|
@@ -179,6 +183,8 @@ export function parseArgs(argv) {
|
|
|
179
183
|
else if (arg === "--help" || arg === "-h") {
|
|
180
184
|
return parsed;
|
|
181
185
|
}
|
|
186
|
+
else if (arg === "--") {
|
|
187
|
+
}
|
|
182
188
|
else if (arg?.startsWith("-")) {
|
|
183
189
|
return { ...parsed, error: `unrecognized arguments: ${arg}` };
|
|
184
190
|
}
|
|
@@ -206,6 +212,14 @@ export function parseArgs(argv) {
|
|
|
206
212
|
error: "--include-closed / --author / --batch-size / --delay-ms / --sample-limit require --mirror (#3125 / #3129)",
|
|
207
213
|
};
|
|
208
214
|
}
|
|
215
|
+
// --ack-discovery is standalone (production entry for tip dismissal without dry-run).
|
|
216
|
+
// Combining with --mirror is allowed: ack runs first, then mirror proceeds.
|
|
217
|
+
if (parsed.ackDiscovery && (parsed.doList || parsed.doValidate) && !parsed.doMirror) {
|
|
218
|
+
return {
|
|
219
|
+
...parsed,
|
|
220
|
+
error: "--ack-discovery cannot combine with --list/--validate without --mirror",
|
|
221
|
+
};
|
|
222
|
+
}
|
|
209
223
|
return parsed;
|
|
210
224
|
}
|
|
211
225
|
/** Run the CLI and return the process exit code. */
|
|
@@ -237,6 +251,29 @@ export function run(argv, options = {}) {
|
|
|
237
251
|
}
|
|
238
252
|
return result.code;
|
|
239
253
|
}
|
|
254
|
+
// #3124: production entry for operator ack (dismiss tip without dry-run).
|
|
255
|
+
if (args.ackDiscovery) {
|
|
256
|
+
try {
|
|
257
|
+
recordMirrorDiscoveryAcked(projectRoot);
|
|
258
|
+
const statePresent = mirrorDiscoveryStateExists(projectRoot);
|
|
259
|
+
if (args.json) {
|
|
260
|
+
process.stdout.write(`${JSON.stringify({ ok: true, ack_discovery: true, projectRoot, statePresent }, null, 2)}\n`);
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
process.stdout.write("[deft triage] SCM label-mirror discovery tip acknowledged " +
|
|
264
|
+
`(state ${statePresent ? "persisted" : "missing"}) — ` +
|
|
265
|
+
"will not re-surface until state is cleared.\n");
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
catch (err) {
|
|
269
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
270
|
+
process.stderr.write(`ERR: failed to record discovery ack: ${msg}\n`);
|
|
271
|
+
return 1;
|
|
272
|
+
}
|
|
273
|
+
if (!args.doMirror) {
|
|
274
|
+
return 0;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
240
277
|
if (args.doMirror) {
|
|
241
278
|
let authorFilter = null;
|
|
242
279
|
// Flag present (including empty `--author=`) must resolve or fail closed —
|
|
@@ -268,6 +305,15 @@ export function run(argv, options = {}) {
|
|
|
268
305
|
else {
|
|
269
306
|
process.stdout.write(renderLabelMirrorReport(outcome));
|
|
270
307
|
}
|
|
308
|
+
// #3124: first successful --mirror dry-run hides the operator discovery tip.
|
|
309
|
+
if (code === 0 && outcome.dry_run) {
|
|
310
|
+
try {
|
|
311
|
+
recordMirrorDiscoverySuccessfulDryRun(projectRoot);
|
|
312
|
+
}
|
|
313
|
+
catch {
|
|
314
|
+
// Advisory tip state — never change the classify exit code.
|
|
315
|
+
}
|
|
316
|
+
}
|
|
271
317
|
return code;
|
|
272
318
|
}
|
|
273
319
|
// Default / --list: print effective rules
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
interface ParsedArgs {
|
|
3
|
+
projectRoot: string;
|
|
4
|
+
xbriefPath: string | null;
|
|
5
|
+
quiet: boolean;
|
|
6
|
+
captureOnly: boolean;
|
|
7
|
+
softMissingXbrief: boolean;
|
|
8
|
+
error?: string;
|
|
9
|
+
}
|
|
10
|
+
/** Parse verify:ac CLI args. */
|
|
11
|
+
export declare function parseArgs(argv: string[]): ParsedArgs;
|
|
12
|
+
/** Result of scanning lifecycle active/ for scope artifacts. */
|
|
13
|
+
export type FindActiveXbriefResult = {
|
|
14
|
+
readonly kind: "one";
|
|
15
|
+
readonly path: string;
|
|
16
|
+
} | {
|
|
17
|
+
readonly kind: "none";
|
|
18
|
+
} | {
|
|
19
|
+
readonly kind: "many";
|
|
20
|
+
readonly paths: readonly string[];
|
|
21
|
+
readonly dir: string;
|
|
22
|
+
};
|
|
23
|
+
/** Run the gate and return the process exit code. */
|
|
24
|
+
export declare function run(argv: string[]): number;
|
|
25
|
+
export {};
|
|
26
|
+
//# sourceMappingURL=verify-ac.d.ts.map
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* CLI: task verify:ac — product-first acceptance criteria gate (#3284).
|
|
4
|
+
*
|
|
5
|
+
* Runs plan.acceptance.commands (or #3267 literal ledger) verbatim.
|
|
6
|
+
* Primary name for the product-first done-gate; verify:literal-ac remains
|
|
7
|
+
* the #3267 mechanism alias.
|
|
8
|
+
*/
|
|
9
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
10
|
+
import { join, resolve } from "node:path";
|
|
11
|
+
import { fileURLToPath } from "node:url";
|
|
12
|
+
import { formatRejectedLedger, resolveLiteralAcceptanceDetailed, } from "@deftai/directive-core/literal-acceptance";
|
|
13
|
+
import { evaluateVerifyAcFromPath, readPlanAcceptance, } from "@deftai/directive-core/product-first-done-gate";
|
|
14
|
+
/** Parse verify:ac CLI args. */
|
|
15
|
+
export function parseArgs(argv) {
|
|
16
|
+
const parsed = {
|
|
17
|
+
projectRoot: ".",
|
|
18
|
+
xbriefPath: null,
|
|
19
|
+
quiet: false,
|
|
20
|
+
captureOnly: false,
|
|
21
|
+
softMissingXbrief: false,
|
|
22
|
+
};
|
|
23
|
+
const positionals = [];
|
|
24
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
25
|
+
const arg = argv[i];
|
|
26
|
+
if (arg === "--quiet") {
|
|
27
|
+
parsed.quiet = true;
|
|
28
|
+
}
|
|
29
|
+
else if (arg === "--capture-only") {
|
|
30
|
+
parsed.captureOnly = true;
|
|
31
|
+
}
|
|
32
|
+
else if (arg === "--soft-missing-xbrief") {
|
|
33
|
+
parsed.softMissingXbrief = true;
|
|
34
|
+
}
|
|
35
|
+
else if (arg === "--project-root") {
|
|
36
|
+
const value = argv[i + 1];
|
|
37
|
+
if (value === undefined) {
|
|
38
|
+
return { ...parsed, error: "argument --project-root: expected one argument" };
|
|
39
|
+
}
|
|
40
|
+
parsed.projectRoot = value;
|
|
41
|
+
i += 1;
|
|
42
|
+
}
|
|
43
|
+
else if (arg?.startsWith("--project-root=")) {
|
|
44
|
+
parsed.projectRoot = arg.slice("--project-root=".length);
|
|
45
|
+
}
|
|
46
|
+
else if (arg === "--xbrief" || arg === "--vbrief") {
|
|
47
|
+
const value = argv[i + 1];
|
|
48
|
+
if (value === undefined) {
|
|
49
|
+
return { ...parsed, error: `argument ${arg}: expected one argument` };
|
|
50
|
+
}
|
|
51
|
+
parsed.xbriefPath = value;
|
|
52
|
+
i += 1;
|
|
53
|
+
}
|
|
54
|
+
else if (arg?.startsWith("--xbrief=") || arg?.startsWith("--vbrief=")) {
|
|
55
|
+
parsed.xbriefPath = arg.slice(arg.indexOf("=") + 1);
|
|
56
|
+
}
|
|
57
|
+
else if (arg?.startsWith("-")) {
|
|
58
|
+
return { ...parsed, error: `unrecognized argument: ${arg}` };
|
|
59
|
+
}
|
|
60
|
+
else if (arg !== undefined) {
|
|
61
|
+
positionals.push(arg);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (parsed.xbriefPath === null && positionals.length > 0) {
|
|
65
|
+
parsed.xbriefPath = positionals[0] ?? null;
|
|
66
|
+
}
|
|
67
|
+
return parsed;
|
|
68
|
+
}
|
|
69
|
+
function listActiveXbriefs(projectRoot) {
|
|
70
|
+
// Greptile conf residual #3284: scan BOTH lifecycle roots (xbrief + read-accepted
|
|
71
|
+
// vbrief). Returning after the first non-empty root left the other unchecked.
|
|
72
|
+
const paths = [];
|
|
73
|
+
const dirs = [];
|
|
74
|
+
for (const dirName of ["xbrief", "vbrief"]) {
|
|
75
|
+
const active = join(projectRoot, dirName, "active");
|
|
76
|
+
if (!existsSync(active))
|
|
77
|
+
continue;
|
|
78
|
+
let names = [];
|
|
79
|
+
try {
|
|
80
|
+
names = readdirSync(active)
|
|
81
|
+
.filter((n) => n.endsWith(".xbrief.json") || n.endsWith(".vbrief.json"))
|
|
82
|
+
.sort();
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (names.length === 0)
|
|
88
|
+
continue;
|
|
89
|
+
dirs.push(active);
|
|
90
|
+
for (const n of names) {
|
|
91
|
+
paths.push(join(active, n));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (paths.length === 0)
|
|
95
|
+
return { kind: "none" };
|
|
96
|
+
if (paths.length === 1) {
|
|
97
|
+
return { kind: "one", path: paths[0] };
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
kind: "many",
|
|
101
|
+
paths,
|
|
102
|
+
dir: dirs.join(" + "),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/** Evaluate one or many xBRIEF paths; return worst non-zero code (fail closed). */
|
|
106
|
+
function evaluatePaths(paths, options) {
|
|
107
|
+
let worst = 0;
|
|
108
|
+
for (const path of paths) {
|
|
109
|
+
if (!options.quiet && paths.length > 1) {
|
|
110
|
+
process.stdout.write(`verify:ac — evaluating ${path}\n`);
|
|
111
|
+
}
|
|
112
|
+
const result = evaluateVerifyAcFromPath(path, {
|
|
113
|
+
projectRoot: options.projectRoot,
|
|
114
|
+
quiet: options.quiet,
|
|
115
|
+
softMissingXbrief: options.softMissingXbrief,
|
|
116
|
+
checkIntegrated: options.softMissingXbrief,
|
|
117
|
+
});
|
|
118
|
+
if (result.message.length > 0) {
|
|
119
|
+
if (result.ok) {
|
|
120
|
+
process.stdout.write(`${result.message}\n`);
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
process.stderr.write(`${result.message}\n`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (result.code !== 0 && (worst === 0 || result.code > worst)) {
|
|
127
|
+
// Prefer code 1 (fail) over 2 when both present — still non-zero.
|
|
128
|
+
worst = result.code;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return worst;
|
|
132
|
+
}
|
|
133
|
+
function asRecord(value) {
|
|
134
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
135
|
+
return value;
|
|
136
|
+
}
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
/** Run the gate and return the process exit code. */
|
|
140
|
+
export function run(argv) {
|
|
141
|
+
const args = parseArgs(argv);
|
|
142
|
+
if (args.error !== undefined) {
|
|
143
|
+
process.stderr.write(`verify_ac: ${args.error}\n`);
|
|
144
|
+
return 2;
|
|
145
|
+
}
|
|
146
|
+
const projectRoot = resolve(args.projectRoot);
|
|
147
|
+
let paths = [];
|
|
148
|
+
if (args.xbriefPath !== null) {
|
|
149
|
+
paths = [resolve(projectRoot, args.xbriefPath)];
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
const found = listActiveXbriefs(projectRoot);
|
|
153
|
+
if (found.kind === "one") {
|
|
154
|
+
paths = [found.path];
|
|
155
|
+
}
|
|
156
|
+
else if (found.kind === "many") {
|
|
157
|
+
// Greptile #3284: multi-active must verify EVERY active scope, not skip.
|
|
158
|
+
if (args.softMissingXbrief || args.captureOnly) {
|
|
159
|
+
paths = [...found.paths];
|
|
160
|
+
if (!args.quiet) {
|
|
161
|
+
process.stdout.write(`verify:ac multi-active (#3284): evaluating ${paths.length} scopes in ${found.dir}\n`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
process.stderr.write(`verify_ac: ${found.paths.length} active scopes in ${found.dir}.\n` +
|
|
166
|
+
" Pass an explicit path, or use check composition (--soft-missing-xbrief) to evaluate all.\n" +
|
|
167
|
+
" Usage: task verify:ac -- <path-to-active.xbrief.json>\n" +
|
|
168
|
+
" Refs #3284 product-first done-gate\n");
|
|
169
|
+
return 1;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
else if (args.softMissingXbrief) {
|
|
173
|
+
if (!args.quiet) {
|
|
174
|
+
process.stdout.write("verify:ac skipped (#3284 soft-missing): no active xBRIEF in xbrief/active/\n");
|
|
175
|
+
}
|
|
176
|
+
return 0;
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
process.stderr.write("verify_ac: pass an xBRIEF path or ensure exactly one artifact in xbrief/active/\n" +
|
|
180
|
+
" Usage: task verify:ac -- <path-to-active.xbrief.json>\n" +
|
|
181
|
+
" Refs #3284 product-first done-gate (mechanism #3267)\n");
|
|
182
|
+
return 2;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if (args.captureOnly) {
|
|
186
|
+
// Capture-only: report each path (multi-active) as a list.
|
|
187
|
+
const reports = [];
|
|
188
|
+
for (const xbriefPath of paths) {
|
|
189
|
+
try {
|
|
190
|
+
const data = asRecord(JSON.parse(readFileSync(xbriefPath, "utf8")));
|
|
191
|
+
if (data === null) {
|
|
192
|
+
process.stderr.write(`verify_ac: xBRIEF top-level is not an object: ${xbriefPath}\n`);
|
|
193
|
+
return 2;
|
|
194
|
+
}
|
|
195
|
+
const plan = asRecord(data.plan);
|
|
196
|
+
if (plan === null) {
|
|
197
|
+
process.stderr.write(`verify_ac: xBRIEF missing plan object: ${xbriefPath}\n`);
|
|
198
|
+
return 2;
|
|
199
|
+
}
|
|
200
|
+
const acceptance = readPlanAcceptance(plan);
|
|
201
|
+
const resolved = resolveLiteralAcceptanceDetailed(plan, { captureFromNarratives: true });
|
|
202
|
+
reports.push({
|
|
203
|
+
xbrief: xbriefPath,
|
|
204
|
+
source_rung: acceptance.source_rung,
|
|
205
|
+
none_stated: acceptance.none_stated,
|
|
206
|
+
acceptance_commands: acceptance.commands,
|
|
207
|
+
count: resolved.commands.length,
|
|
208
|
+
rejected_count: resolved.rejected.length,
|
|
209
|
+
commands: resolved.commands.map((c) => ({
|
|
210
|
+
command: c.command,
|
|
211
|
+
source: c.source,
|
|
212
|
+
sourceSpan: c.sourceSpan ?? null,
|
|
213
|
+
cwd: c.cwd ?? null,
|
|
214
|
+
expectedExitCode: c.expectedExitCode ?? 0,
|
|
215
|
+
})),
|
|
216
|
+
rejected: resolved.rejected.map((r) => ({
|
|
217
|
+
command: r.command,
|
|
218
|
+
reason: r.reason,
|
|
219
|
+
sourceSpan: r.sourceSpan ?? null,
|
|
220
|
+
})),
|
|
221
|
+
});
|
|
222
|
+
if (resolved.rejected.length > 0) {
|
|
223
|
+
process.stderr.write(`${formatRejectedLedger(resolved.rejected)}\n`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
catch (err) {
|
|
227
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
228
|
+
process.stderr.write(`verify_ac: ${msg}\n`);
|
|
229
|
+
return 2;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
process.stdout.write(`${JSON.stringify(reports.length === 1 ? reports[0] : { scopes: reports }, null, 2)}\n`);
|
|
233
|
+
return 0;
|
|
234
|
+
}
|
|
235
|
+
return evaluatePaths(paths, {
|
|
236
|
+
projectRoot,
|
|
237
|
+
quiet: args.quiet,
|
|
238
|
+
softMissingXbrief: args.softMissingXbrief,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
if (process.argv[1] !== undefined && fileURLToPath(import.meta.url) === process.argv[1]) {
|
|
242
|
+
process.exit(run(process.argv.slice(2)));
|
|
243
|
+
}
|
|
244
|
+
//# sourceMappingURL=verify-ac.js.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
interface ParsedArgs {
|
|
3
|
+
projectRoot: string;
|
|
4
|
+
repo: string | null;
|
|
5
|
+
tip: string | null;
|
|
6
|
+
quiet: boolean;
|
|
7
|
+
skipGh: boolean;
|
|
8
|
+
error?: string;
|
|
9
|
+
}
|
|
10
|
+
/** Parse verify-completed-tracked CLI args (#3264). */
|
|
11
|
+
export declare function parseArgs(argv: string[]): ParsedArgs;
|
|
12
|
+
/** Run the gate and return the process exit code. */
|
|
13
|
+
export declare function run(argv: string[]): number;
|
|
14
|
+
export {};
|
|
15
|
+
//# sourceMappingURL=verify-completed-tracked.d.ts.map
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { evaluateCompletedTracked } from "@deftai/directive-core/lifecycle";
|
|
5
|
+
/** Parse verify-completed-tracked CLI args (#3264). */
|
|
6
|
+
export function parseArgs(argv) {
|
|
7
|
+
const parsed = {
|
|
8
|
+
projectRoot: ".",
|
|
9
|
+
repo: null,
|
|
10
|
+
tip: null,
|
|
11
|
+
quiet: false,
|
|
12
|
+
skipGh: false,
|
|
13
|
+
};
|
|
14
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
15
|
+
const arg = argv[i];
|
|
16
|
+
if (arg === "--quiet") {
|
|
17
|
+
parsed.quiet = true;
|
|
18
|
+
}
|
|
19
|
+
else if (arg === "--skip-gh") {
|
|
20
|
+
parsed.skipGh = true;
|
|
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 if (arg === "--repo") {
|
|
34
|
+
const value = argv[i + 1];
|
|
35
|
+
if (value === undefined) {
|
|
36
|
+
return { ...parsed, error: "argument --repo: expected one argument" };
|
|
37
|
+
}
|
|
38
|
+
parsed.repo = value;
|
|
39
|
+
i += 1;
|
|
40
|
+
}
|
|
41
|
+
else if (arg?.startsWith("--repo=")) {
|
|
42
|
+
parsed.repo = arg.slice("--repo=".length);
|
|
43
|
+
}
|
|
44
|
+
else if (arg === "--tip") {
|
|
45
|
+
const value = argv[i + 1];
|
|
46
|
+
if (value === undefined) {
|
|
47
|
+
return { ...parsed, error: "argument --tip: expected one argument" };
|
|
48
|
+
}
|
|
49
|
+
parsed.tip = value;
|
|
50
|
+
i += 1;
|
|
51
|
+
}
|
|
52
|
+
else if (arg?.startsWith("--tip=")) {
|
|
53
|
+
parsed.tip = arg.slice("--tip=".length);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
return { ...parsed, error: `unrecognized argument: ${arg}` };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return parsed;
|
|
60
|
+
}
|
|
61
|
+
/** Run the gate and return the process exit code. */
|
|
62
|
+
export function run(argv) {
|
|
63
|
+
const args = parseArgs(argv);
|
|
64
|
+
if (args.error !== undefined) {
|
|
65
|
+
process.stderr.write(`verify_completed_tracked: ${args.error}\n`);
|
|
66
|
+
return 2;
|
|
67
|
+
}
|
|
68
|
+
const projectRoot = resolve(args.projectRoot);
|
|
69
|
+
const result = evaluateCompletedTracked(projectRoot, {
|
|
70
|
+
quiet: args.quiet,
|
|
71
|
+
repo: args.repo,
|
|
72
|
+
tip: args.tip,
|
|
73
|
+
skipGh: args.skipGh,
|
|
74
|
+
});
|
|
75
|
+
if (result.message.length > 0) {
|
|
76
|
+
if (result.stream === "stdout") {
|
|
77
|
+
process.stdout.write(`${result.message}\n`);
|
|
78
|
+
}
|
|
79
|
+
else if (result.stream === "stderr") {
|
|
80
|
+
process.stderr.write(`${result.message}\n`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return result.code;
|
|
84
|
+
}
|
|
85
|
+
if (process.argv[1] !== undefined && fileURLToPath(import.meta.url) === process.argv[1]) {
|
|
86
|
+
process.exit(run(process.argv.slice(2)));
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=verify-completed-tracked.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
interface ParsedArgs {
|
|
3
|
+
projectRoot: string;
|
|
4
|
+
xbriefPath: string | null;
|
|
5
|
+
quiet: boolean;
|
|
6
|
+
captureOnly: boolean;
|
|
7
|
+
error?: string;
|
|
8
|
+
}
|
|
9
|
+
/** Parse verify-literal-ac CLI args. */
|
|
10
|
+
export declare function parseArgs(argv: string[]): ParsedArgs;
|
|
11
|
+
/** Run the gate and return the process exit code. */
|
|
12
|
+
export declare function run(argv: string[]): number;
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=verify-literal-ac.d.ts.map
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* CLI: task verify:literal-ac — run stored literal acceptance commands verbatim (#3267).
|
|
4
|
+
*/
|
|
5
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
6
|
+
import { join, resolve } from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
import { evaluateLiteralAcceptanceFromPath, formatRejectedLedger, resolveLiteralAcceptanceDetailed, } from "@deftai/directive-core/literal-acceptance";
|
|
9
|
+
/** Parse verify-literal-ac CLI args. */
|
|
10
|
+
export function parseArgs(argv) {
|
|
11
|
+
const parsed = {
|
|
12
|
+
projectRoot: ".",
|
|
13
|
+
xbriefPath: null,
|
|
14
|
+
quiet: false,
|
|
15
|
+
captureOnly: false,
|
|
16
|
+
};
|
|
17
|
+
const positionals = [];
|
|
18
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
19
|
+
const arg = argv[i];
|
|
20
|
+
if (arg === "--quiet") {
|
|
21
|
+
parsed.quiet = true;
|
|
22
|
+
}
|
|
23
|
+
else if (arg === "--capture-only") {
|
|
24
|
+
parsed.captureOnly = true;
|
|
25
|
+
}
|
|
26
|
+
else if (arg === "--project-root") {
|
|
27
|
+
const value = argv[i + 1];
|
|
28
|
+
if (value === undefined) {
|
|
29
|
+
return { ...parsed, error: "argument --project-root: expected one argument" };
|
|
30
|
+
}
|
|
31
|
+
parsed.projectRoot = value;
|
|
32
|
+
i += 1;
|
|
33
|
+
}
|
|
34
|
+
else if (arg?.startsWith("--project-root=")) {
|
|
35
|
+
parsed.projectRoot = arg.slice("--project-root=".length);
|
|
36
|
+
}
|
|
37
|
+
else if (arg === "--xbrief" || arg === "--vbrief") {
|
|
38
|
+
const value = argv[i + 1];
|
|
39
|
+
if (value === undefined) {
|
|
40
|
+
return { ...parsed, error: `argument ${arg}: expected one argument` };
|
|
41
|
+
}
|
|
42
|
+
parsed.xbriefPath = value;
|
|
43
|
+
i += 1;
|
|
44
|
+
}
|
|
45
|
+
else if (arg?.startsWith("--xbrief=") || arg?.startsWith("--vbrief=")) {
|
|
46
|
+
parsed.xbriefPath = arg.slice(arg.indexOf("=") + 1);
|
|
47
|
+
}
|
|
48
|
+
else if (arg?.startsWith("-")) {
|
|
49
|
+
return { ...parsed, error: `unrecognized argument: ${arg}` };
|
|
50
|
+
}
|
|
51
|
+
else if (arg !== undefined) {
|
|
52
|
+
positionals.push(arg);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (parsed.xbriefPath === null && positionals.length > 0) {
|
|
56
|
+
parsed.xbriefPath = positionals[0] ?? null;
|
|
57
|
+
}
|
|
58
|
+
return parsed;
|
|
59
|
+
}
|
|
60
|
+
function findActiveXbrief(projectRoot) {
|
|
61
|
+
for (const dirName of ["xbrief", "vbrief"]) {
|
|
62
|
+
const active = join(projectRoot, dirName, "active");
|
|
63
|
+
if (!existsSync(active))
|
|
64
|
+
continue;
|
|
65
|
+
let names = [];
|
|
66
|
+
try {
|
|
67
|
+
names = readdirSync(active).filter((n) => n.endsWith(".xbrief.json") || n.endsWith(".vbrief.json"));
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (names.length === 1) {
|
|
73
|
+
return join(active, names[0]);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
function asRecord(value) {
|
|
79
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
80
|
+
return value;
|
|
81
|
+
}
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
/** Run the gate and return the process exit code. */
|
|
85
|
+
export function run(argv) {
|
|
86
|
+
const args = parseArgs(argv);
|
|
87
|
+
if (args.error !== undefined) {
|
|
88
|
+
process.stderr.write(`verify_literal_ac: ${args.error}\n`);
|
|
89
|
+
return 2;
|
|
90
|
+
}
|
|
91
|
+
const projectRoot = resolve(args.projectRoot);
|
|
92
|
+
let xbriefPath = args.xbriefPath !== null ? resolve(projectRoot, args.xbriefPath) : null;
|
|
93
|
+
if (xbriefPath === null) {
|
|
94
|
+
xbriefPath = findActiveXbrief(projectRoot);
|
|
95
|
+
}
|
|
96
|
+
if (xbriefPath === null) {
|
|
97
|
+
process.stderr.write("verify_literal_ac: pass an xBRIEF path or ensure exactly one artifact in xbrief/active/\n" +
|
|
98
|
+
" Usage: task verify:literal-ac -- <path-to-active.xbrief.json>\n" +
|
|
99
|
+
" Refs #3267 literal acceptance-command verification\n");
|
|
100
|
+
return 2;
|
|
101
|
+
}
|
|
102
|
+
if (args.captureOnly) {
|
|
103
|
+
try {
|
|
104
|
+
const data = asRecord(JSON.parse(readFileSync(xbriefPath, "utf8")));
|
|
105
|
+
if (data === null) {
|
|
106
|
+
process.stderr.write("verify_literal_ac: xBRIEF top-level is not an object\n");
|
|
107
|
+
return 2;
|
|
108
|
+
}
|
|
109
|
+
const plan = asRecord(data.plan);
|
|
110
|
+
if (plan === null) {
|
|
111
|
+
process.stderr.write("verify_literal_ac: xBRIEF missing plan object\n");
|
|
112
|
+
return 2;
|
|
113
|
+
}
|
|
114
|
+
const resolved = resolveLiteralAcceptanceDetailed(plan, { captureFromNarratives: true });
|
|
115
|
+
const payload = {
|
|
116
|
+
xbrief: xbriefPath,
|
|
117
|
+
count: resolved.commands.length,
|
|
118
|
+
rejected_count: resolved.rejected.length,
|
|
119
|
+
commands: resolved.commands.map((c) => ({
|
|
120
|
+
command: c.command,
|
|
121
|
+
source: c.source,
|
|
122
|
+
sourceSpan: c.sourceSpan ?? null,
|
|
123
|
+
cwd: c.cwd ?? null,
|
|
124
|
+
expectedExitCode: c.expectedExitCode ?? 0,
|
|
125
|
+
})),
|
|
126
|
+
rejected: resolved.rejected.map((r) => ({
|
|
127
|
+
command: r.command,
|
|
128
|
+
reason: r.reason,
|
|
129
|
+
sourceSpan: r.sourceSpan ?? null,
|
|
130
|
+
})),
|
|
131
|
+
};
|
|
132
|
+
process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
|
|
133
|
+
if (resolved.rejected.length > 0) {
|
|
134
|
+
process.stderr.write(`${formatRejectedLedger(resolved.rejected)}\n`);
|
|
135
|
+
}
|
|
136
|
+
return 0;
|
|
137
|
+
}
|
|
138
|
+
catch (err) {
|
|
139
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
140
|
+
process.stderr.write(`verify_literal_ac: ${msg}\n`);
|
|
141
|
+
return 2;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
const result = evaluateLiteralAcceptanceFromPath(xbriefPath, {
|
|
145
|
+
projectRoot,
|
|
146
|
+
quiet: args.quiet,
|
|
147
|
+
});
|
|
148
|
+
if (result.message.length > 0) {
|
|
149
|
+
if (result.ok) {
|
|
150
|
+
process.stdout.write(`${result.message}\n`);
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
process.stderr.write(`${result.message}\n`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return result.code;
|
|
157
|
+
}
|
|
158
|
+
if (process.argv[1] !== undefined && fileURLToPath(import.meta.url) === process.argv[1]) {
|
|
159
|
+
process.exit(run(process.argv.slice(2)));
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=verify-literal-ac.js.map
|
|
@@ -148,6 +148,7 @@ export function run(argv) {
|
|
|
148
148
|
allocationContext: allocationContextText,
|
|
149
149
|
allowDirty: args.allowDirty,
|
|
150
150
|
parsed,
|
|
151
|
+
projectRoot,
|
|
151
152
|
});
|
|
152
153
|
if (result.exitCode === 0 && !args.skipRouting) {
|
|
153
154
|
const provider = resolveDispatchProvider(process.env);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deftai/directive",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.101.0",
|
|
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.101.0",
|
|
38
|
+
"@deftai/directive-content": "^0.101.0"
|
|
39
39
|
},
|
|
40
40
|
"scripts": {
|
|
41
41
|
"build": "tsc -b"
|