@deftai/directive 0.100.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 +3 -0
- package/dist/session-start.d.ts +5 -0
- package/dist/session-start.js +7 -0
- 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/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-completed-tracked", "verify-literal-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"];
|
|
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
|
@@ -139,6 +139,7 @@ export const CLI_MODULE_VERBS = [
|
|
|
139
139
|
"verify-orphan-active",
|
|
140
140
|
"verify-completed-tracked",
|
|
141
141
|
"verify-literal-ac",
|
|
142
|
+
"verify-ac",
|
|
142
143
|
"verify-agents-md-budget",
|
|
143
144
|
"verify-agents-md-advisory",
|
|
144
145
|
"verify-eval-health-relocation",
|
|
@@ -284,6 +285,8 @@ export const VERB_ALIASES = {
|
|
|
284
285
|
"verify:completed-tracked": "verify-completed-tracked",
|
|
285
286
|
"verify:literal-ac": "verify-literal-ac",
|
|
286
287
|
"verify:literal-acceptance": "verify-literal-ac",
|
|
288
|
+
"verify:ac": "verify-ac",
|
|
289
|
+
"verify:acceptance": "verify-ac",
|
|
287
290
|
"verify:agents-md-budget": "verify-agents-md-budget",
|
|
288
291
|
"verify:agents-md-advisory": "verify-agents-md-advisory",
|
|
289
292
|
"verify:eval-health-relocation": "verify-eval-health-relocation",
|
package/dist/session-start.d.ts
CHANGED
|
@@ -27,6 +27,11 @@ export interface ParsedSessionStartArgs {
|
|
|
27
27
|
maxBudget?: number;
|
|
28
28
|
hardBudget?: boolean;
|
|
29
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;
|
|
30
35
|
error?: string;
|
|
31
36
|
}
|
|
32
37
|
/** Parse session:start CLI args, mirroring scripts/session_start.py. */
|
package/dist/session-start.js
CHANGED
|
@@ -30,6 +30,7 @@ export function parseArgs(argv) {
|
|
|
30
30
|
ceremonyDialInputs: {},
|
|
31
31
|
ceremonyDepthOverride: null,
|
|
32
32
|
effortBudgetHost: {},
|
|
33
|
+
compact: false,
|
|
33
34
|
};
|
|
34
35
|
const dialInputs = {};
|
|
35
36
|
for (let i = 0; i < argv.length; i += 1) {
|
|
@@ -268,6 +269,10 @@ export function parseArgs(argv) {
|
|
|
268
269
|
else if (arg === "--hard-budget") {
|
|
269
270
|
parsed.effortBudgetHost.hardBudget = true;
|
|
270
271
|
}
|
|
272
|
+
else if (arg === "--compact") {
|
|
273
|
+
// #3286: terse machine orientation output (opt-in; verbose remains default)
|
|
274
|
+
parsed.compact = true;
|
|
275
|
+
}
|
|
271
276
|
else {
|
|
272
277
|
return { ...parsed, error: `unrecognized argument: ${arg}` };
|
|
273
278
|
}
|
|
@@ -328,6 +333,8 @@ export function run(argv) {
|
|
|
328
333
|
ceremonyTier: args.ceremonyTier,
|
|
329
334
|
ceremonyDialInputs: args.ceremonyDialInputs,
|
|
330
335
|
effortBudgetSeams: { hostDescriptor, environ: process.env },
|
|
336
|
+
// #3286: --compact flag; DEFT_SESSION_COMPACT still resolved inside core.
|
|
337
|
+
compact: args.compact ? true : undefined,
|
|
331
338
|
...(args.ceremonyDepthOverride !== null
|
|
332
339
|
? {
|
|
333
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
|
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"
|